Merge "Only allow libconnectivity_native usage on U+"
diff --git a/Cronet/Android.bp b/Cronet/Android.bp
deleted file mode 100644
index 3ce88ef..0000000
--- a/Cronet/Android.bp
+++ /dev/null
@@ -1,106 +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_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_import {
-    name: "cronet_impl_native_java",
-    jars: ["prebuilt/cronet_impl_native_java.jar"],
-    visibility: ["//visibility:private"],
-    apex_available: ["com.android.tethering"],
-    min_sdk_version: "29",
-}
-
-java_import {
-    name: "cronet_impl_common_java",
-    jars: ["prebuilt/cronet_impl_common_java.jar"],
-    visibility: ["//visibility:private"],
-    apex_available: ["com.android.tethering"],
-    min_sdk_version: "29",
-}
-
-java_import {
-    name: "cronet_impl_platform_java",
-    jars: ["prebuilt/cronet_impl_platform_java.jar"],
-    visibility: ["//visibility:private"],
-    apex_available: ["com.android.tethering"],
-    min_sdk_version: "29",
-}
-
-cc_prebuilt_library_shared {
-    name: "libcronet.107.0.5284.2",
-    shared_libs: [
-        "libandroid",
-        "libc",
-        "libdl",
-        "liblog",
-        "libm",
-    ],
-    stl: "libc++_static",
-    target: {
-        android_arm64: {
-            srcs: ["prebuilt/libs/arm64-v8a/libcronet.107.0.5284.2.so"],
-        },
-        android_arm: {
-            srcs: ["prebuilt/libs/armeabi-v7a/libcronet.107.0.5284.2.so"],
-        },
-        android_x86_64: {
-            srcs: ["prebuilt/libs/x86_64/libcronet.107.0.5284.2.so"],
-        },
-        android_x86: {
-            srcs: ["prebuilt/libs/x86/libcronet.107.0.5284.2.so"],
-        },
-    },
-    // These are already stripped, and restripping them just issues diagnostics.
-    strip: {
-        none: true,
-    },
-    apex_available: ["com.android.tethering"],
-    min_sdk_version: "29",
-}
-
-genrule {
-    name: "cronet_api-src",
-    srcs: ["prebuilt/cronet_api-src.jar"],
-    cmd: "cp $(in) $(out)",
-    out: [
-        "cronet_api-src.srcjar",
-    ],
-}
-
-java_sdk_library {
-    name: "framework-cronet",
-    defaults: ["framework-module-defaults"],
-    srcs: [
-        ":cronet_api-src",
-    ],
-    libs: [
-        "androidx.annotation_annotation",
-    ],
-    static_libs: [
-        "androidx.core_core-nodeps",
-        "cronet_impl_common_java",
-        "cronet_impl_native_java",
-        "cronet_impl_platform_java",
-    ],
-    apex_available: ["com.android.tethering"],
-    jarjar_rules: "jarjar-rules.txt",
-    unsafe_ignore_missing_latest_api: true,
-    dist_group: "android",
-    // cronet is used as a shared library.
-    shared_library: true,
-    exclude_kotlinc_generated_files: true,
-}
diff --git a/Cronet/TEST_MAPPING b/Cronet/TEST_MAPPING
deleted file mode 100644
index 815d496..0000000
--- a/Cronet/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "postsubmit": [
-    {
-      "name": "CronetApiTest"
-    }
-  ]
-}
diff --git a/Cronet/apex/Android.bp b/Cronet/apex/Android.bp
deleted file mode 100644
index 180dafb..0000000
--- a/Cronet/apex/Android.bp
+++ /dev/null
@@ -1,59 +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_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-// CronetApexDefaults uses apex_defaults cronet_apex_defaults specifies. cronet_apex_defaults
-// could be "CronetApexDefaultsEnabled" or "CronetApexDefaultsDisabled" depending on the branch.
-cronet_apex_defaults = "CronetApexDefaultsEnabled"
-// This is a placeholder comment to avoid merge conflicts
-// as cronet_apex_defaults may have different values
-// depending on the branch
-
-apex_defaults {
-    name: "CronetApexDefaults",
-    defaults: [cronet_apex_defaults],
-}
-
-apex_defaults {
-    name: "CronetApexDefaultsEnabled",
-    jni_libs: ["libcronet.107.0.5284.2"],
-    java_libs: ["framework-cronet"],
-    arch: {
-        riscv64: {
-            // TODO: remove this when there is a riscv64 libcronet
-            exclude_jni_libs: ["libcronet.107.0.5284.2"],
-        },
-    },
-}
-
-apex_defaults {
-    name: "CronetApexDefaultsDisabled",
-}
-
-// TODO: Remove cronet apex after com.android.cronet is removed from PRODUCT_PACKAGES
-apex {
-    name: "com.android.cronet",
-    key: "com.android.cronet.key",
-    updatable: false,
-    manifest: "manifest.json",
-}
-
-apex_key {
-    name: "com.android.cronet.key",
-    public_key: "com.android.cronet.avbpubkey",
-    private_key: "com.android.cronet.pem",
-}
diff --git a/Cronet/apex/AndroidManifest.xml b/Cronet/apex/AndroidManifest.xml
deleted file mode 100644
index 650badc..0000000
--- a/Cronet/apex/AndroidManifest.xml
+++ /dev/null
@@ -1,24 +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="com.android.cronet">
-  <!-- APEX does not have classes.dex -->
-  <application android:hasCode="false" />
-  <uses-sdk
-      android:minSdkVersion="29"
-  />
-</manifest>
diff --git a/Cronet/apex/com.android.cronet.avbpubkey b/Cronet/apex/com.android.cronet.avbpubkey
deleted file mode 100644
index 38aebe0..0000000
--- a/Cronet/apex/com.android.cronet.avbpubkey
+++ /dev/null
Binary files differ
diff --git a/Cronet/apex/com.android.cronet.pem b/Cronet/apex/com.android.cronet.pem
deleted file mode 100644
index 438653f..0000000
--- a/Cronet/apex/com.android.cronet.pem
+++ /dev/null
@@ -1,51 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIJKAIBAAKCAgEAqpxkMMK57w+fzxLcwF+mEQEbKrDFWXYHL697tv8DBu/aL2tM
-LRHiKFdov0Fsnup++bd9oojI+6qyAyJh4I8nvzKc4onM0eXL++0FPZuiTv6a6r7K
-wyn+NVT2X/0yr2Hs5NL1rKXFmJPAfMoRCW9vQdi5xMlM9QN8mNaqSWddKtrQM3yC
-HDLy2zZd7MlQ1UTnmDqGCG0AVtuM34X0v1o0wAL7UwNdzOARtmnuWzcL5vBwAJg8
-eXFZH5Pt5rITxbU+eYw/V+/sUYyI0Anrj4GG+oyWQxgdxz+FwdpSAt14xtw5IDl7
-pTYqEIm+TQKnWe6NzVWfI89s2nh85hgOSgugfpS4symbfbZd4qNSOcHGQTB1ssvf
-vdYGF1WRFA20VEEOMzTMYsvcfModKNVCpibzHu/SYDIJGL0JyJWu2eIE+mrn3lLD
-nwDZ2P39YyVNpritEkbk5qugaLh9mIFqAD4L4niYLu/AtYq+CXzd7yroM2ycleRq
-JRNsCIUx3A/Z5vKx7IJaMb7Pwap3DAe3u3/2L9NCL6oo074Rf7fh5xkDF4Dua3Gg
-kPw+k183jAQECHORstdVlgVZlMxPif4lxQ8uCHJWxyDsgShPgzMtEjJyrJJQyDon
-X8pN4xqb+WX30XNYBK5sp7x6mc8w4rdAGExhUSdmTS0J/lcfar6A6/j/yg8CAwEA
-AQKCAgAiYafTJ7q+kWB8I2n3Ho9hx95IqRzsHVvvYSbGRve+MyG+App0TrFLvemu
-+SlBkTILcs3Prk8KYGjFNu2QimjRIAr7oBd1iSClYSt4Md/wmWBwxAgqclD3QGry
-Bx1quIo7xsOZikKar9PPkg0C4MED/P/ax1JJ4ez/A+uHJVxiIXxpk8LImf/U60zc
-RemTQPKG++w80HKMDmyCMwWSdkRBGZi6Luh9O/51yz0shphQbs2zYPp24r+6HF6J
-6gMQCalQZ1Hwj7oI6RA9FHKzFcA0x5YUaUy+9W8oFK4IQ8duE70zYEIplhO+B3Qh
-ItLEzc0nvwR1+/wMvtE0sU5X36X06AFhkcpjyAdmNVMiG91KGrZqruOufrOQu4VE
-njJ5VEUvWOr+6inZDWIdT3NQvwJCZkT4Vvn9WBAoKM8pkpfhY5HTGq4ttX7McHjF
-p7YBFbHRpzO+OfSwM7f/xq2WLcjOKgsFgv17CUo7KQo1rqWPD/4IKJKW5n6dzDwX
-RRF4dehUMYK2UNAbcN9S8O8nJGcJfb76FExjZtGLrhlDgawLmu71bbq5afot/IQ9
-JuB4KxG59t2JAHGNgM7WfFvcL1Zp1D+kzDEHYKYkyDJh3qB7Tw+StlAQFSQjNBX/
-c1I6DUl68rSHOc2LF4oK8ID1oe/URMg2YoaLlF8un2nGZBfwGQKCAQEA20Ie5hBC
-H8W1vTtyI4jOdn/h11RL8UuLN0xtXYunYCzD4+gAEKsAShbhKnvINThZaoxQ2sxy
-9EB+2Ob8R9muYAxR4Eu4tDBWedYmJEl74MflLMFnIrtFIKy4F54zk7J6uNG3cHRo
-yTpoIcmK101xzz1Ed9Dd0XL6rpegnWIuVlWV4slGAf1l3h6pycfx+HCYcCboz6Nq
-JMA0ioKY0fgz5q+mb7IIObN8JjGdLeBQtNh3fcXby08AT/03psKhjgBsbuyTs7s7
-E3n+jxJ3p4xHP/psU3D/HgKWewp48AJNHY8MLY+Kp0KNoQMSQKHKqsiluGTCKLTJ
-sCWg/2c8xf728wKCAQEAxzNdZ54e6WXexMMIsGoH+wVknTHiizuxN4ZqzY/nCM0M
-9heooChfMAFrq8XMC/6MXgiy5rBhl3H12HlG4S6cq3J07MsMR0N+sUFEobuZURB4
-+CbEBebmXx1kymC6FrRG4JuKZWbQ8AKpfd28QO7x915safeq6FA8cILJn4oqBGqQ
-Y8TzMKtuaCzQGoBjSgiMpx1fo9Stl7+dpGiPNsnQEG+SEsXxKoZweJPsuK8G+ZgB
-8YARajLwFfgfsCNp/8fXjA98BYM8eSxURa7USUyCmY4hU3WIGG5qc0/C69Fl35ex
-YZG7XQo1DxW/+gWsdUbAFbI4y+iJ8SRpsFxtFYifdQKCAQA5DiW4PHbYibxXN8bl
-1E3VrEV6oSb57WyWwT6cXyD49+0pu095BuaWYQnK4lcg8j7iaQ0JQraPNNFNZB42
-HEEyIUKVGV9BFGsMXVujibPAtIPAd7t84DqG3Csziillv8YLnhccHk6+PoKmeCm3
-CSIaiZjtjN6MCF2PXUmgatIgCTltwG6FSgleGaCZL3yZ58LjPFzM23tdgN6rRHy7
-9tiaqQ6odi2JxlkCH1sFex/FT6cYhYpCh5ZPOldm/7LGnvmYi9uLo6cl1FMXq/iT
-Ev/feC0EMZ1Rk97QudLqsc6baIQEvxuXlswAICp5wyBX/MqTBzU3HoR1X/VbQOQh
-qc1dAoIBACLjNhqtsNBDzS480krDZz5phWOalwi3naQR4Ka760S5VOnM3vWd3H31
-4bul2sTHAiJ994c7oPv7M4mERAuwNDQ6yYunTDE2+vtkaPbCemmeLvGXKIG4HOTP
-qxVet3i+fiNcWnLD/Rfr/29R5GSi9LHUUbyFaeNiGhPCdDmC4zT+zOcMWWNOwvlv
-z8q0ba9LrAaguF1jJDwNjTh8L4jy84PNZpHvJPvDq/MSRUVbMieInd6EBYjJ/w55
-9GLO8QOhJnkbRSdaAr9eKixCIF/uDHmEUQXi8cEFpZMohwTyGZt9X82szlnPLdfE
-gWjykW/AwmeKXTQpN++J5xDCP0CkOvkCggEBAND4wviHue/+bqY9R5EAIDIZn6kx
-VCK9roebvGjq/Az5AM7IiyUQRANv+6CmeIlXCRBMbaAtgQxhvW5UodA1livgVuzR
-ElF7Br3wfikZ34oWY0Qu8fZI0ru4syAoTiGpaUPJJgStYi76dqne15usTRk/MvwP
-tJzqpkWUYcmNv3g/w92Wr1nIJYXlPKrANSSppRg0P/CAPOHkPxLF0RwC5yIZzC5C
-hiTXSE9AwCFllMRKInnbhUdy/L+xUL6mAbGVvD/DHE7Q2xmPSsdEX5nTZkhEnW/L
-TLLgbsy2+8ouvMJCSNaMq77jq7iCIMTogEfA5GX0UO2Kjf4pnjREOzNQg4I=
------END RSA PRIVATE KEY-----
diff --git a/Cronet/apex/com.android.cronet.pk8 b/Cronet/apex/com.android.cronet.pk8
deleted file mode 100644
index a63d761..0000000
--- a/Cronet/apex/com.android.cronet.pk8
+++ /dev/null
Binary files differ
diff --git a/Cronet/apex/com.android.cronet.x509.pem b/Cronet/apex/com.android.cronet.x509.pem
deleted file mode 100644
index c9cd874..0000000
--- a/Cronet/apex/com.android.cronet.x509.pem
+++ /dev/null
@@ -1,35 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIGIzCCBAugAwIBAgIUKEbkVLro9rIJE3M71D8sgUIngUIwDQYJKoZIhvcNAQEL
-BQAwgZ8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
-DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
-b2lkMRswGQYDVQQDDBJjb20uYW5kcm9pZC5jcm9uZXQxIjAgBgkqhkiG9w0BCQEW
-E2FuZHJvaWRAYW5kcm9pZC5jb20wIBcNMTkxMjA1MDcwOTIxWhgPNDc1NzEwMzEw
-NzA5MjFaMIGfMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQG
-A1UEBwwNTW91bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwH
-QW5kcm9pZDEbMBkGA1UEAwwSY29tLmFuZHJvaWQuY3JvbmV0MSIwIAYJKoZIhvcN
-AQkBFhNhbmRyb2lkQGFuZHJvaWQuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
-MIICCgKCAgEAwNXIHR2BeZl/R7tOXgC3KK71rrtK98zucfrNBNp/rUiFqiN527P4
-HLW+CAwnFJGLLzHhSbXiZLXyLS7FfPuMeiULUsv1mIVEVwVhtPvbodxwh5szxlVe
-iVf1WkIV90n2tZojTnhMsiEPt0EBWriPJstdO264snqg+oY9Dktxk4tXtTah/rUI
-KWPHeB0SWQMXZTKb30CVVHZfpG/HCXjUlVLx+14/X56EGi8fxd13q5c56qAybx73
-i6+Sm3+F/jcivDpATuPluvcPaZ4Tel9Nz5NjqjycyXBXh8f8azpN1GkJkltE116W
-ZT1iVIJoDAd540UXhW+TpjTeaEH5OeDOWorQM6nE1N9FMiyReprU7tz+HVmyD44Z
-ah43whi3gmwyjgzscW2Z0xGpgVoHgfz/RyBg5+w6p5AGPR0sewv6zr9HvQhpJsoj
-pl+HD9xcrdGb2yMiwm8RuH9dGW0Nos6p1LiXsxg6VBic3N7S+MGXo/dXdX39tkad
-tD+lNskJUJqyx70ynQtRI66YicyxsFv0BImBC+eGECT3hCgyyxnlvTAeRsVsEWtV
-Z+Xwf5M53/8uXkqGal7cC1bHEkG+0zsR8cc0U+22Nif9HQNIrsjEnybOlP6erV/M
-8yX9umVwDz5QlVKCYJFQOq+awSqR9KD9VX3xIRu8kB6aPtB4A7q3828CAwEAAaNT
-MFEwHQYDVR0OBBYEFBQBcdf/bfLa3knW1ukCOFOt++FtMB8GA1UdIwQYMBaAFBQB
-cdf/bfLa3knW1ukCOFOt++FtMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
-BQADggIBALG0r81xb6NwaGCF79SeDecbnDE1MbQ2+cRt+eU/Wl2SI2oQqbaa/WP7
-k3vsZvsBFI6r+ZBiKizpbT/N1Fx8pcoH7rsL0MoZRXaCZY6kQJLZN6AAvXEo4NR0
-p01zDvGibVk3z87R98IJHlDPU3NrljxxMCsaUxFEAjae4eS4j8G2TFm6DsTVTeqy
-zveh4NdZFsZSyUN8blChFC/7TytlizeSGNlXLMx8e/VAcxIw+wQPWLk5pNnk3TXu
-HcqwAryW/lt46+iVeTpF4ylFqYyab6Vmf9Vp6+HVatg7YkErJACeoeN4eRv6DfeY
-slMqa2i+W+veVAGP4VApg7iZE5RnNPPHn/80yATKAe0/AztoZpQWZ5g1IAnpCISg
-k65FHVhbkcKdjNQePOGwjictq21KaYFrbXfIwqpejxSqQdLMne02Dj/2kv0dU1jV
-JG9YxVWIpobauYD5mmZvN6PTYJWGQRAsIFFyWfhvnEiohGmVLNEQ3uRaue0yNMXc
-V2t8B81/jNGXwi06qxmjCMnMhucSCSFl2a2V5oiDEcVj4YbaJ+CEJPdopMhQwqab
-EgTkJBeEXoPFenToSIprL/YiuBNvvPETyTYN+hAXh3P7MSeWewkgGhpZhuyzT8F0
-vEo+nt5WhRWl0pEVLFLiOOXR+/LAB3aqrPEdd0NXvxiCb/QaZnCp
------END CERTIFICATE-----
diff --git a/Cronet/apex/manifest.json b/Cronet/apex/manifest.json
deleted file mode 100644
index 0301e9f..0000000
--- a/Cronet/apex/manifest.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "name": "com.android.cronet",
-  "version": 1
-}
diff --git a/Cronet/api/current.txt b/Cronet/api/current.txt
deleted file mode 100644
index 21779bc..0000000
--- a/Cronet/api/current.txt
+++ /dev/null
@@ -1,175 +0,0 @@
-// Signature format: 2.0
-package org.chromium.net {
-
-  public abstract class CallbackException extends org.chromium.net.CronetException {
-    ctor protected CallbackException(String, Throwable);
-  }
-
-  public abstract class CronetEngine {
-    ctor public CronetEngine();
-    method public abstract java.net.URLStreamHandlerFactory createURLStreamHandlerFactory();
-    method public abstract byte[] getGlobalMetricsDeltas();
-    method public abstract String getVersionString();
-    method public abstract org.chromium.net.UrlRequest.Builder newUrlRequestBuilder(String, org.chromium.net.UrlRequest.Callback, java.util.concurrent.Executor);
-    method public abstract java.net.URLConnection openConnection(java.net.URL) throws java.io.IOException;
-    method public abstract void shutdown();
-    method public abstract void startNetLogToFile(String, boolean);
-    method public abstract void stopNetLog();
-  }
-
-  public static class CronetEngine.Builder {
-    ctor public CronetEngine.Builder(android.content.Context);
-    method public org.chromium.net.CronetEngine.Builder addPublicKeyPins(String, java.util.Set<byte[]>, boolean, java.util.Date);
-    method public org.chromium.net.CronetEngine.Builder addQuicHint(String, int, int);
-    method public org.chromium.net.CronetEngine build();
-    method public org.chromium.net.CronetEngine.Builder enableBrotli(boolean);
-    method public org.chromium.net.CronetEngine.Builder enableHttp2(boolean);
-    method public org.chromium.net.CronetEngine.Builder enableHttpCache(int, long);
-    method public org.chromium.net.CronetEngine.Builder enablePublicKeyPinningBypassForLocalTrustAnchors(boolean);
-    method public org.chromium.net.CronetEngine.Builder enableQuic(boolean);
-    method public String getDefaultUserAgent();
-    method public org.chromium.net.CronetEngine.Builder setLibraryLoader(org.chromium.net.CronetEngine.Builder.LibraryLoader);
-    method public org.chromium.net.CronetEngine.Builder setStoragePath(String);
-    method public org.chromium.net.CronetEngine.Builder setUserAgent(String);
-    field public static final int HTTP_CACHE_DISABLED = 0; // 0x0
-    field public static final int HTTP_CACHE_DISK = 3; // 0x3
-    field public static final int HTTP_CACHE_DISK_NO_HTTP = 2; // 0x2
-    field public static final int HTTP_CACHE_IN_MEMORY = 1; // 0x1
-  }
-
-  public abstract static class CronetEngine.Builder.LibraryLoader {
-    ctor public CronetEngine.Builder.LibraryLoader();
-    method public abstract void loadLibrary(String);
-  }
-
-  public abstract class CronetException extends java.io.IOException {
-    ctor protected CronetException(String, Throwable);
-  }
-
-  public final class InlineExecutionProhibitedException extends java.util.concurrent.RejectedExecutionException {
-    ctor public InlineExecutionProhibitedException();
-  }
-
-  public abstract class NetworkException extends org.chromium.net.CronetException {
-    ctor protected NetworkException(String, Throwable);
-    method public abstract int getCronetInternalErrorCode();
-    method public abstract int getErrorCode();
-    method public abstract boolean immediatelyRetryable();
-    field public static final int ERROR_ADDRESS_UNREACHABLE = 9; // 0x9
-    field public static final int ERROR_CONNECTION_CLOSED = 5; // 0x5
-    field public static final int ERROR_CONNECTION_REFUSED = 7; // 0x7
-    field public static final int ERROR_CONNECTION_RESET = 8; // 0x8
-    field public static final int ERROR_CONNECTION_TIMED_OUT = 6; // 0x6
-    field public static final int ERROR_HOSTNAME_NOT_RESOLVED = 1; // 0x1
-    field public static final int ERROR_INTERNET_DISCONNECTED = 2; // 0x2
-    field public static final int ERROR_NETWORK_CHANGED = 3; // 0x3
-    field public static final int ERROR_OTHER = 11; // 0xb
-    field public static final int ERROR_QUIC_PROTOCOL_FAILED = 10; // 0xa
-    field public static final int ERROR_TIMED_OUT = 4; // 0x4
-  }
-
-  public abstract class QuicException extends org.chromium.net.NetworkException {
-    ctor protected QuicException(String, Throwable);
-    method public abstract int getQuicDetailedErrorCode();
-  }
-
-  public abstract class UploadDataProvider implements java.io.Closeable {
-    ctor public UploadDataProvider();
-    method public void close() throws java.io.IOException;
-    method public abstract long getLength() throws java.io.IOException;
-    method public abstract void read(org.chromium.net.UploadDataSink, java.nio.ByteBuffer) throws java.io.IOException;
-    method public abstract void rewind(org.chromium.net.UploadDataSink) throws java.io.IOException;
-  }
-
-  public final class UploadDataProviders {
-    method public static org.chromium.net.UploadDataProvider create(java.io.File);
-    method public static org.chromium.net.UploadDataProvider create(android.os.ParcelFileDescriptor);
-    method public static org.chromium.net.UploadDataProvider create(java.nio.ByteBuffer);
-    method public static org.chromium.net.UploadDataProvider create(byte[], int, int);
-    method public static org.chromium.net.UploadDataProvider create(byte[]);
-  }
-
-  public abstract class UploadDataSink {
-    ctor public UploadDataSink();
-    method public abstract void onReadError(Exception);
-    method public abstract void onReadSucceeded(boolean);
-    method public abstract void onRewindError(Exception);
-    method public abstract void onRewindSucceeded();
-  }
-
-  public abstract class UrlRequest {
-    ctor public UrlRequest();
-    method public abstract void cancel();
-    method public abstract void followRedirect();
-    method public abstract void getStatus(org.chromium.net.UrlRequest.StatusListener);
-    method public abstract boolean isDone();
-    method public abstract void read(java.nio.ByteBuffer);
-    method public abstract void start();
-  }
-
-  public abstract static class UrlRequest.Builder {
-    ctor public UrlRequest.Builder();
-    method public abstract org.chromium.net.UrlRequest.Builder addHeader(String, String);
-    method public abstract org.chromium.net.UrlRequest.Builder allowDirectExecutor();
-    method public abstract org.chromium.net.UrlRequest build();
-    method public abstract org.chromium.net.UrlRequest.Builder disableCache();
-    method public abstract org.chromium.net.UrlRequest.Builder setHttpMethod(String);
-    method public abstract org.chromium.net.UrlRequest.Builder setPriority(int);
-    method public abstract org.chromium.net.UrlRequest.Builder setUploadDataProvider(org.chromium.net.UploadDataProvider, java.util.concurrent.Executor);
-    field public static final int REQUEST_PRIORITY_HIGHEST = 4; // 0x4
-    field public static final int REQUEST_PRIORITY_IDLE = 0; // 0x0
-    field public static final int REQUEST_PRIORITY_LOW = 2; // 0x2
-    field public static final int REQUEST_PRIORITY_LOWEST = 1; // 0x1
-    field public static final int REQUEST_PRIORITY_MEDIUM = 3; // 0x3
-  }
-
-  public abstract static class UrlRequest.Callback {
-    ctor public UrlRequest.Callback();
-    method public void onCanceled(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo);
-    method public abstract void onFailed(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo, org.chromium.net.CronetException);
-    method public abstract void onReadCompleted(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo, java.nio.ByteBuffer) throws java.lang.Exception;
-    method public abstract void onRedirectReceived(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo, String) throws java.lang.Exception;
-    method public abstract void onResponseStarted(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo) throws java.lang.Exception;
-    method public abstract void onSucceeded(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo);
-  }
-
-  public static class UrlRequest.Status {
-    field public static final int CONNECTING = 10; // 0xa
-    field public static final int DOWNLOADING_PAC_FILE = 5; // 0x5
-    field public static final int ESTABLISHING_PROXY_TUNNEL = 8; // 0x8
-    field public static final int IDLE = 0; // 0x0
-    field public static final int INVALID = -1; // 0xffffffff
-    field public static final int READING_RESPONSE = 14; // 0xe
-    field public static final int RESOLVING_HOST = 9; // 0x9
-    field public static final int RESOLVING_HOST_IN_PAC_FILE = 7; // 0x7
-    field public static final int RESOLVING_PROXY_FOR_URL = 6; // 0x6
-    field public static final int SENDING_REQUEST = 12; // 0xc
-    field public static final int SSL_HANDSHAKE = 11; // 0xb
-    field public static final int WAITING_FOR_AVAILABLE_SOCKET = 2; // 0x2
-    field public static final int WAITING_FOR_CACHE = 4; // 0x4
-    field public static final int WAITING_FOR_DELEGATE = 3; // 0x3
-    field public static final int WAITING_FOR_RESPONSE = 13; // 0xd
-    field public static final int WAITING_FOR_STALLED_SOCKET_POOL = 1; // 0x1
-  }
-
-  public abstract static class UrlRequest.StatusListener {
-    ctor public UrlRequest.StatusListener();
-    method public abstract void onStatus(int);
-  }
-
-  public abstract class UrlResponseInfo {
-    ctor public UrlResponseInfo();
-    method public abstract java.util.Map<java.lang.String,java.util.List<java.lang.String>> getAllHeaders();
-    method public abstract java.util.List<java.util.Map.Entry<java.lang.String,java.lang.String>> getAllHeadersAsList();
-    method public abstract int getHttpStatusCode();
-    method public abstract String getHttpStatusText();
-    method public abstract String getNegotiatedProtocol();
-    method public abstract String getProxyServer();
-    method public abstract long getReceivedByteCount();
-    method public abstract String getUrl();
-    method public abstract java.util.List<java.lang.String> getUrlChain();
-    method public abstract boolean wasCached();
-  }
-
-}
-
diff --git a/Cronet/api/lint-baseline.txt b/Cronet/api/lint-baseline.txt
deleted file mode 100644
index 0e2f25b..0000000
--- a/Cronet/api/lint-baseline.txt
+++ /dev/null
@@ -1,263 +0,0 @@
-// Baseline format: 1.0
-AcronymName: org.chromium.net.CronetEngine#createURLStreamHandlerFactory():
-    Acronyms should not be capitalized in method names: was `createURLStreamHandlerFactory`, should this be `createUrlStreamHandlerFactory`?
-
-
-AndroidUri: org.chromium.net.CronetEngine#createURLStreamHandlerFactory():
-    Use android.net.Uri instead of java.net.URL (method org.chromium.net.CronetEngine.createURLStreamHandlerFactory())
-AndroidUri: org.chromium.net.CronetEngine#openConnection(java.net.URL):
-    Use android.net.Uri instead of java.net.URL (method org.chromium.net.CronetEngine.openConnection(java.net.URL))
-AndroidUri: org.chromium.net.CronetEngine#openConnection(java.net.URL) parameter #0:
-    Use android.net.Uri instead of java.net.URL (parameter url in org.chromium.net.CronetEngine.openConnection(java.net.URL url))
-
-
-BuilderSetStyle: org.chromium.net.CronetEngine.Builder#enableBrotli(boolean):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method org.chromium.net.CronetEngine.Builder.enableBrotli(boolean)
-BuilderSetStyle: org.chromium.net.CronetEngine.Builder#enableHttp2(boolean):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method org.chromium.net.CronetEngine.Builder.enableHttp2(boolean)
-BuilderSetStyle: org.chromium.net.CronetEngine.Builder#enableHttpCache(int, long):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method org.chromium.net.CronetEngine.Builder.enableHttpCache(int,long)
-BuilderSetStyle: org.chromium.net.CronetEngine.Builder#enablePublicKeyPinningBypassForLocalTrustAnchors(boolean):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method org.chromium.net.CronetEngine.Builder.enablePublicKeyPinningBypassForLocalTrustAnchors(boolean)
-BuilderSetStyle: org.chromium.net.CronetEngine.Builder#enableQuic(boolean):
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method org.chromium.net.CronetEngine.Builder.enableQuic(boolean)
-BuilderSetStyle: org.chromium.net.UrlRequest.Builder#allowDirectExecutor():
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method org.chromium.net.UrlRequest.Builder.allowDirectExecutor()
-BuilderSetStyle: org.chromium.net.UrlRequest.Builder#disableCache():
-    Builder methods names should use setFoo() / addFoo() / clearFoo() style: method org.chromium.net.UrlRequest.Builder.disableCache()
-
-
-ExecutorRegistration: org.chromium.net.UrlRequest#getStatus(org.chromium.net.UrlRequest.StatusListener):
-    Registration methods should have overload that accepts delivery Executor: `getStatus`
-
-
-GenericException: org.chromium.net.UrlRequest.Callback#onReadCompleted(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo, java.nio.ByteBuffer):
-    Methods must not throw generic exceptions (`java.lang.Exception`)
-GenericException: org.chromium.net.UrlRequest.Callback#onRedirectReceived(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo, String):
-    Methods must not throw generic exceptions (`java.lang.Exception`)
-GenericException: org.chromium.net.UrlRequest.Callback#onResponseStarted(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo):
-    Methods must not throw generic exceptions (`java.lang.Exception`)
-
-
-GetterOnBuilder: org.chromium.net.CronetEngine.Builder#getDefaultUserAgent():
-    Getter should be on the built object, not the builder: method org.chromium.net.CronetEngine.Builder.getDefaultUserAgent()
-
-
-ListenerInterface: org.chromium.net.UrlRequest.StatusListener:
-    Listeners should be an interface, or otherwise renamed Callback: StatusListener
-
-
-ListenerLast: org.chromium.net.CronetEngine#newUrlRequestBuilder(String, org.chromium.net.UrlRequest.Callback, java.util.concurrent.Executor) parameter #2:
-    Listeners should always be at end of argument list (method `newUrlRequestBuilder`)
-
-
-MissingGetterMatchingBuilder: org.chromium.net.CronetEngine.Builder#addPublicKeyPins(String, java.util.Set<byte[]>, boolean, java.util.Date):
-    org.chromium.net.CronetEngine does not declare a `getPublicKeyPinss()` method matching method org.chromium.net.CronetEngine.Builder.addPublicKeyPins(String,java.util.Set<byte[]>,boolean,java.util.Date)
-MissingGetterMatchingBuilder: org.chromium.net.CronetEngine.Builder#addQuicHint(String, int, int):
-    org.chromium.net.CronetEngine does not declare a `getQuicHints()` method matching method org.chromium.net.CronetEngine.Builder.addQuicHint(String,int,int)
-MissingGetterMatchingBuilder: org.chromium.net.CronetEngine.Builder#setLibraryLoader(org.chromium.net.CronetEngine.Builder.LibraryLoader):
-    org.chromium.net.CronetEngine does not declare a `getLibraryLoader()` method matching method org.chromium.net.CronetEngine.Builder.setLibraryLoader(org.chromium.net.CronetEngine.Builder.LibraryLoader)
-MissingGetterMatchingBuilder: org.chromium.net.CronetEngine.Builder#setStoragePath(String):
-    org.chromium.net.CronetEngine does not declare a `getStoragePath()` method matching method org.chromium.net.CronetEngine.Builder.setStoragePath(String)
-MissingGetterMatchingBuilder: org.chromium.net.CronetEngine.Builder#setUserAgent(String):
-    org.chromium.net.CronetEngine does not declare a `getUserAgent()` method matching method org.chromium.net.CronetEngine.Builder.setUserAgent(String)
-MissingGetterMatchingBuilder: org.chromium.net.UrlRequest.Builder#addHeader(String, String):
-    org.chromium.net.UrlRequest does not declare a `getHeaders()` method matching method org.chromium.net.UrlRequest.Builder.addHeader(String,String)
-MissingGetterMatchingBuilder: org.chromium.net.UrlRequest.Builder#setHttpMethod(String):
-    org.chromium.net.UrlRequest does not declare a `getHttpMethod()` method matching method org.chromium.net.UrlRequest.Builder.setHttpMethod(String)
-MissingGetterMatchingBuilder: org.chromium.net.UrlRequest.Builder#setPriority(int):
-    org.chromium.net.UrlRequest does not declare a `getPriority()` method matching method org.chromium.net.UrlRequest.Builder.setPriority(int)
-MissingGetterMatchingBuilder: org.chromium.net.UrlRequest.Builder#setUploadDataProvider(org.chromium.net.UploadDataProvider, java.util.concurrent.Executor):
-    org.chromium.net.UrlRequest does not declare a `getUploadDataProvider()` method matching method org.chromium.net.UrlRequest.Builder.setUploadDataProvider(org.chromium.net.UploadDataProvider,java.util.concurrent.Executor)
-
-
-MissingNullability: org.chromium.net.CallbackException#CallbackException(String, Throwable) parameter #0:
-    Missing nullability on parameter `message` in method `CallbackException`
-MissingNullability: org.chromium.net.CallbackException#CallbackException(String, Throwable) parameter #1:
-    Missing nullability on parameter `cause` in method `CallbackException`
-MissingNullability: org.chromium.net.CronetEngine#createURLStreamHandlerFactory():
-    Missing nullability on method `createURLStreamHandlerFactory` return
-MissingNullability: org.chromium.net.CronetEngine#getGlobalMetricsDeltas():
-    Missing nullability on method `getGlobalMetricsDeltas` return
-MissingNullability: org.chromium.net.CronetEngine#getVersionString():
-    Missing nullability on method `getVersionString` return
-MissingNullability: org.chromium.net.CronetEngine#newUrlRequestBuilder(String, org.chromium.net.UrlRequest.Callback, java.util.concurrent.Executor):
-    Missing nullability on method `newUrlRequestBuilder` return
-MissingNullability: org.chromium.net.CronetEngine#newUrlRequestBuilder(String, org.chromium.net.UrlRequest.Callback, java.util.concurrent.Executor) parameter #0:
-    Missing nullability on parameter `url` in method `newUrlRequestBuilder`
-MissingNullability: org.chromium.net.CronetEngine#newUrlRequestBuilder(String, org.chromium.net.UrlRequest.Callback, java.util.concurrent.Executor) parameter #1:
-    Missing nullability on parameter `callback` in method `newUrlRequestBuilder`
-MissingNullability: org.chromium.net.CronetEngine#newUrlRequestBuilder(String, org.chromium.net.UrlRequest.Callback, java.util.concurrent.Executor) parameter #2:
-    Missing nullability on parameter `executor` in method `newUrlRequestBuilder`
-MissingNullability: org.chromium.net.CronetEngine#openConnection(java.net.URL):
-    Missing nullability on method `openConnection` return
-MissingNullability: org.chromium.net.CronetEngine#openConnection(java.net.URL) parameter #0:
-    Missing nullability on parameter `url` in method `openConnection`
-MissingNullability: org.chromium.net.CronetEngine#startNetLogToFile(String, boolean) parameter #0:
-    Missing nullability on parameter `fileName` in method `startNetLogToFile`
-MissingNullability: org.chromium.net.CronetEngine.Builder#Builder(android.content.Context) parameter #0:
-    Missing nullability on parameter `context` in method `Builder`
-MissingNullability: org.chromium.net.CronetEngine.Builder#addPublicKeyPins(String, java.util.Set<byte[]>, boolean, java.util.Date):
-    Missing nullability on method `addPublicKeyPins` return
-MissingNullability: org.chromium.net.CronetEngine.Builder#addPublicKeyPins(String, java.util.Set<byte[]>, boolean, java.util.Date) parameter #0:
-    Missing nullability on parameter `hostName` in method `addPublicKeyPins`
-MissingNullability: org.chromium.net.CronetEngine.Builder#addPublicKeyPins(String, java.util.Set<byte[]>, boolean, java.util.Date) parameter #1:
-    Missing nullability on parameter `pinsSha256` in method `addPublicKeyPins`
-MissingNullability: org.chromium.net.CronetEngine.Builder#addPublicKeyPins(String, java.util.Set<byte[]>, boolean, java.util.Date) parameter #3:
-    Missing nullability on parameter `expirationDate` in method `addPublicKeyPins`
-MissingNullability: org.chromium.net.CronetEngine.Builder#addQuicHint(String, int, int):
-    Missing nullability on method `addQuicHint` return
-MissingNullability: org.chromium.net.CronetEngine.Builder#addQuicHint(String, int, int) parameter #0:
-    Missing nullability on parameter `host` in method `addQuicHint`
-MissingNullability: org.chromium.net.CronetEngine.Builder#build():
-    Missing nullability on method `build` return
-MissingNullability: org.chromium.net.CronetEngine.Builder#enableBrotli(boolean):
-    Missing nullability on method `enableBrotli` return
-MissingNullability: org.chromium.net.CronetEngine.Builder#enableHttp2(boolean):
-    Missing nullability on method `enableHttp2` return
-MissingNullability: org.chromium.net.CronetEngine.Builder#enableHttpCache(int, long):
-    Missing nullability on method `enableHttpCache` return
-MissingNullability: org.chromium.net.CronetEngine.Builder#enablePublicKeyPinningBypassForLocalTrustAnchors(boolean):
-    Missing nullability on method `enablePublicKeyPinningBypassForLocalTrustAnchors` return
-MissingNullability: org.chromium.net.CronetEngine.Builder#enableQuic(boolean):
-    Missing nullability on method `enableQuic` return
-MissingNullability: org.chromium.net.CronetEngine.Builder#getDefaultUserAgent():
-    Missing nullability on method `getDefaultUserAgent` return
-MissingNullability: org.chromium.net.CronetEngine.Builder#setLibraryLoader(org.chromium.net.CronetEngine.Builder.LibraryLoader):
-    Missing nullability on method `setLibraryLoader` return
-MissingNullability: org.chromium.net.CronetEngine.Builder#setLibraryLoader(org.chromium.net.CronetEngine.Builder.LibraryLoader) parameter #0:
-    Missing nullability on parameter `loader` in method `setLibraryLoader`
-MissingNullability: org.chromium.net.CronetEngine.Builder#setStoragePath(String):
-    Missing nullability on method `setStoragePath` return
-MissingNullability: org.chromium.net.CronetEngine.Builder#setStoragePath(String) parameter #0:
-    Missing nullability on parameter `value` in method `setStoragePath`
-MissingNullability: org.chromium.net.CronetEngine.Builder#setUserAgent(String):
-    Missing nullability on method `setUserAgent` return
-MissingNullability: org.chromium.net.CronetEngine.Builder#setUserAgent(String) parameter #0:
-    Missing nullability on parameter `userAgent` in method `setUserAgent`
-MissingNullability: org.chromium.net.CronetEngine.Builder.LibraryLoader#loadLibrary(String) parameter #0:
-    Missing nullability on parameter `libName` in method `loadLibrary`
-MissingNullability: org.chromium.net.CronetException#CronetException(String, Throwable) parameter #0:
-    Missing nullability on parameter `message` in method `CronetException`
-MissingNullability: org.chromium.net.CronetException#CronetException(String, Throwable) parameter #1:
-    Missing nullability on parameter `cause` in method `CronetException`
-MissingNullability: org.chromium.net.NetworkException#NetworkException(String, Throwable) parameter #0:
-    Missing nullability on parameter `message` in method `NetworkException`
-MissingNullability: org.chromium.net.NetworkException#NetworkException(String, Throwable) parameter #1:
-    Missing nullability on parameter `cause` in method `NetworkException`
-MissingNullability: org.chromium.net.QuicException#QuicException(String, Throwable) parameter #0:
-    Missing nullability on parameter `message` in method `QuicException`
-MissingNullability: org.chromium.net.QuicException#QuicException(String, Throwable) parameter #1:
-    Missing nullability on parameter `cause` in method `QuicException`
-MissingNullability: org.chromium.net.UploadDataProvider#read(org.chromium.net.UploadDataSink, java.nio.ByteBuffer) parameter #0:
-    Missing nullability on parameter `uploadDataSink` in method `read`
-MissingNullability: org.chromium.net.UploadDataProvider#read(org.chromium.net.UploadDataSink, java.nio.ByteBuffer) parameter #1:
-    Missing nullability on parameter `byteBuffer` in method `read`
-MissingNullability: org.chromium.net.UploadDataProvider#rewind(org.chromium.net.UploadDataSink) parameter #0:
-    Missing nullability on parameter `uploadDataSink` in method `rewind`
-MissingNullability: org.chromium.net.UploadDataProviders#create(android.os.ParcelFileDescriptor):
-    Missing nullability on method `create` return
-MissingNullability: org.chromium.net.UploadDataProviders#create(android.os.ParcelFileDescriptor) parameter #0:
-    Missing nullability on parameter `fd` in method `create`
-MissingNullability: org.chromium.net.UploadDataProviders#create(byte[]):
-    Missing nullability on method `create` return
-MissingNullability: org.chromium.net.UploadDataProviders#create(byte[]) parameter #0:
-    Missing nullability on parameter `data` in method `create`
-MissingNullability: org.chromium.net.UploadDataProviders#create(byte[], int, int):
-    Missing nullability on method `create` return
-MissingNullability: org.chromium.net.UploadDataProviders#create(byte[], int, int) parameter #0:
-    Missing nullability on parameter `data` in method `create`
-MissingNullability: org.chromium.net.UploadDataProviders#create(java.io.File):
-    Missing nullability on method `create` return
-MissingNullability: org.chromium.net.UploadDataProviders#create(java.io.File) parameter #0:
-    Missing nullability on parameter `file` in method `create`
-MissingNullability: org.chromium.net.UploadDataProviders#create(java.nio.ByteBuffer):
-    Missing nullability on method `create` return
-MissingNullability: org.chromium.net.UploadDataProviders#create(java.nio.ByteBuffer) parameter #0:
-    Missing nullability on parameter `buffer` in method `create`
-MissingNullability: org.chromium.net.UploadDataSink#onReadError(Exception) parameter #0:
-    Missing nullability on parameter `exception` in method `onReadError`
-MissingNullability: org.chromium.net.UploadDataSink#onRewindError(Exception) parameter #0:
-    Missing nullability on parameter `exception` in method `onRewindError`
-MissingNullability: org.chromium.net.UrlRequest#getStatus(org.chromium.net.UrlRequest.StatusListener) parameter #0:
-    Missing nullability on parameter `listener` in method `getStatus`
-MissingNullability: org.chromium.net.UrlRequest#read(java.nio.ByteBuffer) parameter #0:
-    Missing nullability on parameter `buffer` in method `read`
-MissingNullability: org.chromium.net.UrlRequest.Builder#addHeader(String, String):
-    Missing nullability on method `addHeader` return
-MissingNullability: org.chromium.net.UrlRequest.Builder#addHeader(String, String) parameter #0:
-    Missing nullability on parameter `header` in method `addHeader`
-MissingNullability: org.chromium.net.UrlRequest.Builder#addHeader(String, String) parameter #1:
-    Missing nullability on parameter `value` in method `addHeader`
-MissingNullability: org.chromium.net.UrlRequest.Builder#allowDirectExecutor():
-    Missing nullability on method `allowDirectExecutor` return
-MissingNullability: org.chromium.net.UrlRequest.Builder#build():
-    Missing nullability on method `build` return
-MissingNullability: org.chromium.net.UrlRequest.Builder#disableCache():
-    Missing nullability on method `disableCache` return
-MissingNullability: org.chromium.net.UrlRequest.Builder#setHttpMethod(String):
-    Missing nullability on method `setHttpMethod` return
-MissingNullability: org.chromium.net.UrlRequest.Builder#setHttpMethod(String) parameter #0:
-    Missing nullability on parameter `method` in method `setHttpMethod`
-MissingNullability: org.chromium.net.UrlRequest.Builder#setPriority(int):
-    Missing nullability on method `setPriority` return
-MissingNullability: org.chromium.net.UrlRequest.Builder#setUploadDataProvider(org.chromium.net.UploadDataProvider, java.util.concurrent.Executor):
-    Missing nullability on method `setUploadDataProvider` return
-MissingNullability: org.chromium.net.UrlRequest.Builder#setUploadDataProvider(org.chromium.net.UploadDataProvider, java.util.concurrent.Executor) parameter #0:
-    Missing nullability on parameter `uploadDataProvider` in method `setUploadDataProvider`
-MissingNullability: org.chromium.net.UrlRequest.Builder#setUploadDataProvider(org.chromium.net.UploadDataProvider, java.util.concurrent.Executor) parameter #1:
-    Missing nullability on parameter `executor` in method `setUploadDataProvider`
-MissingNullability: org.chromium.net.UrlRequest.Callback#onCanceled(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo) parameter #0:
-    Missing nullability on parameter `request` in method `onCanceled`
-MissingNullability: org.chromium.net.UrlRequest.Callback#onCanceled(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo) parameter #1:
-    Missing nullability on parameter `info` in method `onCanceled`
-MissingNullability: org.chromium.net.UrlRequest.Callback#onFailed(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo, org.chromium.net.CronetException) parameter #0:
-    Missing nullability on parameter `request` in method `onFailed`
-MissingNullability: org.chromium.net.UrlRequest.Callback#onFailed(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo, org.chromium.net.CronetException) parameter #1:
-    Missing nullability on parameter `info` in method `onFailed`
-MissingNullability: org.chromium.net.UrlRequest.Callback#onFailed(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo, org.chromium.net.CronetException) parameter #2:
-    Missing nullability on parameter `error` in method `onFailed`
-MissingNullability: org.chromium.net.UrlRequest.Callback#onReadCompleted(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo, java.nio.ByteBuffer) parameter #0:
-    Missing nullability on parameter `request` in method `onReadCompleted`
-MissingNullability: org.chromium.net.UrlRequest.Callback#onReadCompleted(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo, java.nio.ByteBuffer) parameter #1:
-    Missing nullability on parameter `info` in method `onReadCompleted`
-MissingNullability: org.chromium.net.UrlRequest.Callback#onReadCompleted(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo, java.nio.ByteBuffer) parameter #2:
-    Missing nullability on parameter `byteBuffer` in method `onReadCompleted`
-MissingNullability: org.chromium.net.UrlRequest.Callback#onRedirectReceived(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo, String) parameter #0:
-    Missing nullability on parameter `request` in method `onRedirectReceived`
-MissingNullability: org.chromium.net.UrlRequest.Callback#onRedirectReceived(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo, String) parameter #1:
-    Missing nullability on parameter `info` in method `onRedirectReceived`
-MissingNullability: org.chromium.net.UrlRequest.Callback#onRedirectReceived(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo, String) parameter #2:
-    Missing nullability on parameter `newLocationUrl` in method `onRedirectReceived`
-MissingNullability: org.chromium.net.UrlRequest.Callback#onResponseStarted(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo) parameter #0:
-    Missing nullability on parameter `request` in method `onResponseStarted`
-MissingNullability: org.chromium.net.UrlRequest.Callback#onResponseStarted(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo) parameter #1:
-    Missing nullability on parameter `info` in method `onResponseStarted`
-MissingNullability: org.chromium.net.UrlRequest.Callback#onSucceeded(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo) parameter #0:
-    Missing nullability on parameter `request` in method `onSucceeded`
-MissingNullability: org.chromium.net.UrlRequest.Callback#onSucceeded(org.chromium.net.UrlRequest, org.chromium.net.UrlResponseInfo) parameter #1:
-    Missing nullability on parameter `info` in method `onSucceeded`
-MissingNullability: org.chromium.net.UrlResponseInfo#getAllHeaders():
-    Missing nullability on method `getAllHeaders` return
-MissingNullability: org.chromium.net.UrlResponseInfo#getAllHeadersAsList():
-    Missing nullability on method `getAllHeadersAsList` return
-MissingNullability: org.chromium.net.UrlResponseInfo#getHttpStatusText():
-    Missing nullability on method `getHttpStatusText` return
-MissingNullability: org.chromium.net.UrlResponseInfo#getNegotiatedProtocol():
-    Missing nullability on method `getNegotiatedProtocol` return
-MissingNullability: org.chromium.net.UrlResponseInfo#getProxyServer():
-    Missing nullability on method `getProxyServer` return
-MissingNullability: org.chromium.net.UrlResponseInfo#getUrl():
-    Missing nullability on method `getUrl` return
-MissingNullability: org.chromium.net.UrlResponseInfo#getUrlChain():
-    Missing nullability on method `getUrlChain` return
-
-
-NotCloseable: org.chromium.net.CronetEngine:
-    Classes that release resources (shutdown()) should implement AutoClosable and CloseGuard: class org.chromium.net.CronetEngine
-
-
-StaticFinalBuilder: org.chromium.net.CronetEngine.Builder:
-    Builder must be final: org.chromium.net.CronetEngine.Builder
-StaticFinalBuilder: org.chromium.net.UrlRequest.Builder:
-    Builder must be final: org.chromium.net.UrlRequest.Builder
diff --git a/Cronet/api/module-lib-current.txt b/Cronet/api/module-lib-current.txt
deleted file mode 100644
index d802177..0000000
--- a/Cronet/api/module-lib-current.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/Cronet/api/removed.txt b/Cronet/api/removed.txt
deleted file mode 100644
index d802177..0000000
--- a/Cronet/api/removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/Cronet/api/system-current.txt b/Cronet/api/system-current.txt
deleted file mode 100644
index d802177..0000000
--- a/Cronet/api/system-current.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/Cronet/api/test-current.txt b/Cronet/api/test-current.txt
deleted file mode 100644
index d802177..0000000
--- a/Cronet/api/test-current.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/Cronet/api/test-removed.txt b/Cronet/api/test-removed.txt
deleted file mode 100644
index d802177..0000000
--- a/Cronet/api/test-removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/Cronet/jarjar-rules.txt b/Cronet/jarjar-rules.txt
deleted file mode 100644
index 7111ebc..0000000
--- a/Cronet/jarjar-rules.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-rule androidx.** com.android.cronet.@0
-rule android.support.** com.android.cronet.@0
-
diff --git a/Cronet/prebuilt/VERSION b/Cronet/prebuilt/VERSION
deleted file mode 100644
index c16ab94..0000000
--- a/Cronet/prebuilt/VERSION
+++ /dev/null
@@ -1,4 +0,0 @@
-MAJOR=80
-MINOR=0
-BUILD=3986
-PATCH=0
diff --git a/Cronet/prebuilt/api_version.txt b/Cronet/prebuilt/api_version.txt
deleted file mode 100644
index 48082f7..0000000
--- a/Cronet/prebuilt/api_version.txt
+++ /dev/null
@@ -1 +0,0 @@
-12
diff --git a/Cronet/prebuilt/cronet_api-src.jar b/Cronet/prebuilt/cronet_api-src.jar
deleted file mode 100644
index 924b877..0000000
--- a/Cronet/prebuilt/cronet_api-src.jar
+++ /dev/null
Binary files differ
diff --git a/Cronet/prebuilt/cronet_api.jar b/Cronet/prebuilt/cronet_api.jar
deleted file mode 100644
index 977b28d..0000000
--- a/Cronet/prebuilt/cronet_api.jar
+++ /dev/null
Binary files differ
diff --git a/Cronet/prebuilt/cronet_impl_common_java.jar b/Cronet/prebuilt/cronet_impl_common_java.jar
deleted file mode 100644
index fa71bf3..0000000
--- a/Cronet/prebuilt/cronet_impl_common_java.jar
+++ /dev/null
Binary files differ
diff --git a/Cronet/prebuilt/cronet_impl_native_java.jar b/Cronet/prebuilt/cronet_impl_native_java.jar
deleted file mode 100644
index 4cdd6f3..0000000
--- a/Cronet/prebuilt/cronet_impl_native_java.jar
+++ /dev/null
Binary files differ
diff --git a/Cronet/prebuilt/cronet_impl_platform_java.jar b/Cronet/prebuilt/cronet_impl_platform_java.jar
deleted file mode 100644
index 6d6042f..0000000
--- a/Cronet/prebuilt/cronet_impl_platform_java.jar
+++ /dev/null
Binary files differ
diff --git a/Cronet/prebuilt/libs/arm64-v8a/libcronet.107.0.5284.2.so b/Cronet/prebuilt/libs/arm64-v8a/libcronet.107.0.5284.2.so
deleted file mode 100644
index 7f2540a..0000000
--- a/Cronet/prebuilt/libs/arm64-v8a/libcronet.107.0.5284.2.so
+++ /dev/null
Binary files differ
diff --git a/Cronet/prebuilt/libs/armeabi-v7a/libcronet.107.0.5284.2.so b/Cronet/prebuilt/libs/armeabi-v7a/libcronet.107.0.5284.2.so
deleted file mode 100644
index 115429a..0000000
--- a/Cronet/prebuilt/libs/armeabi-v7a/libcronet.107.0.5284.2.so
+++ /dev/null
Binary files differ
diff --git a/Cronet/prebuilt/libs/x86/libcronet.107.0.5284.2.so b/Cronet/prebuilt/libs/x86/libcronet.107.0.5284.2.so
deleted file mode 100644
index 27923f7..0000000
--- a/Cronet/prebuilt/libs/x86/libcronet.107.0.5284.2.so
+++ /dev/null
Binary files differ
diff --git a/Cronet/prebuilt/libs/x86_64/libcronet.107.0.5284.2.so b/Cronet/prebuilt/libs/x86_64/libcronet.107.0.5284.2.so
deleted file mode 100644
index 803e5cd..0000000
--- a/Cronet/prebuilt/libs/x86_64/libcronet.107.0.5284.2.so
+++ /dev/null
Binary files differ
diff --git a/Cronet/tests/OWNERS b/Cronet/tests/OWNERS
new file mode 100644
index 0000000..acb6ee6
--- /dev/null
+++ b/Cronet/tests/OWNERS
@@ -0,0 +1,8 @@
+# Bug component: 31808
+
+set noparent
+file:platform/packages/modules/Connectivity:master:/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
new file mode 100644
index 0000000..e17081a
--- /dev/null
+++ b/Cronet/tests/common/Android.bp
@@ -0,0 +1,42 @@
+// 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 {
+    // 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"
+    ],
+}
diff --git a/Cronet/tests/common/AndroidManifest.xml b/Cronet/tests/common/AndroidManifest.xml
new file mode 100644
index 0000000..b00fc90
--- /dev/null
+++ b/Cronet/tests/common/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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
new file mode 100644
index 0000000..2ac418f
--- /dev/null
+++ b/Cronet/tests/common/AndroidTest.xml
@@ -0,0 +1,36 @@
+<!--
+  ~ 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" />
+    <!-- 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" />
+        <option name="hidden-api-checks" value="false"/>
+        <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
index e71c707..1e0adef 100644
--- a/Cronet/tests/cts/Android.bp
+++ b/Cronet/tests/cts/Android.bp
@@ -18,29 +18,49 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-java_library {
-    name: "CronetApiCommonTests",
-    srcs: ["src/**/*.java"],
+android_library {
+    name: "CtsNetHttpTestsLib",
+    defaults: [
+        "cts_defaults",
+    ],
+    sdk_version: "test_current",
+    min_sdk_version: "30",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
     static_libs: [
-        "androidx.test.rules",
-        "androidx.core_core",
+        "androidx.test.ext.junit",
+        "ctstestrunner-axt",
+        "ctstestserver",
+        "hamcrest-library",
         "junit",
+        "kotlin-test",
+        "mockito-target",
+        "truth",
     ],
     libs: [
-        "android.test.runner",
         "android.test.base",
-        "android.test.mock",
         "androidx.annotation_annotation",
-        "framework-cronet",
+        "framework-connectivity",
+        "org.apache.http.legacy",
     ],
+    lint: { test: true }
 }
 
 android_test {
-    name: "CronetApiTest",
+    name: "CtsNetHttpTestCases",
+    defaults: [
+        "cts_defaults",
+    ],
+    enforce_default_target_sdk_version: true,
     sdk_version: "test_current",
-    test_suites: ["device-tests"],
-    certificate: "platform",
-    static_libs: [
-        "CronetApiCommonTests",
+    min_sdk_version: "30",
+    static_libs: ["CtsNetHttpTestsLib"],
+    // Tag this as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts-tethering"
     ],
 }
diff --git a/Cronet/tests/cts/AndroidManifest.xml b/Cronet/tests/cts/AndroidManifest.xml
index db0f0b3..26900b2 100644
--- a/Cronet/tests/cts/AndroidManifest.xml
+++ b/Cronet/tests/cts/AndroidManifest.xml
@@ -18,18 +18,18 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="org.chromium.net.test">
+          package="android.net.http.cts">
 
     <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
 
-    <application>
-        <uses-library android:name="android.test.runner" />
-        <uses-library android:name="framework-cronet" />
+    <application android:networkSecurityConfig="@xml/network_security_config">
+        <uses-library android:name="android.test.runner"/>
     </application>
 
     <instrumentation
         android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="org.chromium.net.test"
-        android:label="Cronet API Networking Tests" />
-</manifest>
\ No newline at end of file
+        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
new file mode 100644
index 0000000..e0421fd
--- /dev/null
+++ b/Cronet/tests/cts/AndroidTest.xml
@@ -0,0 +1,38 @@
+<?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
new file mode 100644
index 0000000..ea62ce2
--- /dev/null
+++ b/Cronet/tests/cts/assets/html/hello_world.html
@@ -0,0 +1,24 @@
+<!--
+  ~ 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
new file mode 100644
index 0000000..7d7530b
--- /dev/null
+++ b/Cronet/tests/cts/res/xml/network_security_config.xml
@@ -0,0 +1,23 @@
+<?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
new file mode 100644
index 0000000..bead1f8
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http.cts
+
+import android.content.Context
+import android.net.http.BidirectionalStream
+import android.net.http.HttpEngine
+import android.net.http.cts.util.TestBidirectionalStreamCallback
+import android.net.http.cts.util.TestBidirectionalStreamCallback.ResponseStep
+import android.net.http.cts.util.assumeOKStatusCode
+import android.net.http.cts.util.skipIfNoInternetConnection
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import org.hamcrest.MatcherAssert
+import org.hamcrest.Matchers
+import org.junit.After
+import org.junit.Before
+import org.junit.runner.RunWith
+
+private const val URL = "https://source.android.com"
+
+/**
+ * This tests uses a non-hermetic server. Instead of asserting, assume the next callback. This way,
+ * if the request were to fail, the test would just be skipped instead of failing.
+ */
+@RunWith(AndroidJUnit4::class)
+class BidirectionalStreamTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val callback = TestBidirectionalStreamCallback()
+    private val httpEngine = HttpEngine.Builder(context).build()
+    private var stream: BidirectionalStream? = null
+
+    @Before
+    fun setUp() {
+        skipIfNoInternetConnection(context)
+    }
+
+    @After
+    @Throws(Exception::class)
+    fun tearDown() {
+        // cancel active requests to enable engine shutdown.
+        stream?.let {
+            it.cancel()
+            callback.blockForDone()
+        }
+        httpEngine.shutdown()
+    }
+
+    private fun createBidirectionalStreamBuilder(url: String): BidirectionalStream.Builder {
+        return httpEngine.newBidirectionalStreamBuilder(url, callback.executor, callback)
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun testBidirectionalStream_GetStream_CompletesSuccessfully() {
+        stream = createBidirectionalStreamBuilder(URL).setHttpMethod("GET").build()
+        stream!!.start()
+        callback.assumeCallback(ResponseStep.ON_SUCCEEDED)
+        val info = callback.mResponseInfo
+        assumeOKStatusCode(info)
+        MatcherAssert.assertThat(
+            "Received byte count must be > 0", info.receivedByteCount, Matchers.greaterThan(0L))
+        assertEquals("h2", info.negotiatedProtocol)
+    }
+}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/CallbackExceptionTest.kt b/Cronet/tests/cts/src/android/net/http/cts/CallbackExceptionTest.kt
new file mode 100644
index 0000000..749389e
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/CallbackExceptionTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http.cts
+
+import android.content.Context
+import android.net.http.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 androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+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(AndroidJUnit4::class)
+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
new file mode 100644
index 0000000..219db61
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/ConnectionMigrationOptionsTest.kt
@@ -0,0 +1,68 @@
+/*
+ * 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ConnectionMigrationOptionsTest {
+
+    @Test
+    fun testConnectionMigrationOptions_defaultValues() {
+        val options =
+                ConnectionMigrationOptions.Builder().build()
+
+        assertEquals(MIGRATION_OPTION_UNSPECIFIED, options.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
new file mode 100644
index 0000000..6f4a979
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/DnsOptionsTest.kt
@@ -0,0 +1,150 @@
+/*
+ * 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import java.time.Duration
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+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
new file mode 100644
index 0000000..31990fb
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/HttpEngineTest.java
@@ -0,0 +1,402 @@
+/*
+ * 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 androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+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();
+        HttpCtsTestServer server =
+                new HttpCtsTestServer(ApplicationProvider.getApplicationContext());
+
+        String url = server.getUserAgentUrl();
+        UrlRequest request =
+                mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback).build();
+        request.start();
+
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+        UrlResponseInfo info = mCallback.mResponseInfo;
+        assertOKStatusCode(info);
+        String receivedUserAgent = extractUserAgent(mCallback.mResponseAsString);
+
+        assertThat(receivedUserAgent).isEqualTo(mEngineBuilder.getDefaultUserAgent());
+    }
+
+    @Test
+    public void testHttpEngine_requestUsesCustomUserAgent() throws Exception {
+        String userAgent = "CtsTests User Agent";
+        HttpCtsTestServer server =
+                new HttpCtsTestServer(ApplicationProvider.getApplicationContext());
+        mEngine =
+                new HttpEngine.Builder(ApplicationProvider.getApplicationContext())
+                        .setUserAgent(userAgent)
+                        .build();
+
+        String url = server.getUserAgentUrl();
+        UrlRequest request =
+                mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback).build();
+        request.start();
+
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+        UrlResponseInfo info = mCallback.mResponseInfo;
+        assertOKStatusCode(info);
+        String receivedUserAgent = extractUserAgent(mCallback.mResponseAsString);
+
+        assertThat(receivedUserAgent).isEqualTo(userAgent);
+    }
+
+    private static String extractUserAgent(String userAgentResponseBody) {
+        // If someone wants to be evil and have the title HTML tag a part of the user agent,
+        // they'll have to fix this method :)
+        return userAgentResponseBody.replaceFirst(".*<title>", "").replaceFirst("</title>.*", "");
+    }
+
+    @Test
+    public void testHttpEngine_bindToNetwork() throws Exception {
+        // Create a fake Android.net.Network. Since that network doesn't exist, binding to
+        // that should end up in a failed request.
+        Network mockNetwork = Mockito.mock(Network.class);
+        Mockito.when(mockNetwork.getNetworkHandle()).thenReturn(123L);
+        String url = mTestServer.getSuccessUrl();
+
+        mEngine = mEngineBuilder.build();
+        mEngine.bindToNetwork(mockNetwork);
+        UrlRequest.Builder builder =
+                mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
+        mRequest = builder.build();
+        mRequest.start();
+
+        mCallback.expectCallback(ResponseStep.ON_FAILED);
+    }
+
+    @Test
+    public void testHttpEngine_unbindFromNetwork() throws Exception {
+        // Create a fake Android.net.Network. Since that network doesn't exist, binding to
+        // that should end up in a failed request.
+        Network mockNetwork = Mockito.mock(Network.class);
+        Mockito.when(mockNetwork.getNetworkHandle()).thenReturn(123L);
+        String url = mTestServer.getSuccessUrl();
+
+        mEngine = mEngineBuilder.build();
+        // Bind to the fake network but then unbind. This should result in a successful
+        // request.
+        mEngine.bindToNetwork(mockNetwork);
+        mEngine.bindToNetwork(null);
+        UrlRequest.Builder builder =
+                mEngine.newUrlRequestBuilder(url, mCallback.getExecutor(), mCallback);
+        mRequest = builder.build();
+        mRequest.start();
+
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+        UrlResponseInfo info = mCallback.mResponseInfo;
+        assertOKStatusCode(info);
+    }
+
+    @Test
+    public void testHttpEngine_setConnectionMigrationOptions_requestSucceeds() {
+        ConnectionMigrationOptions options = new ConnectionMigrationOptions.Builder().build();
+        mEngine = mEngineBuilder.setConnectionMigrationOptions(options).build();
+        UrlRequest.Builder builder =
+                mEngine.newUrlRequestBuilder(
+                        mTestServer.getSuccessUrl(), mCallback.getExecutor(), mCallback);
+        mRequest = builder.build();
+        mRequest.start();
+
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+        UrlResponseInfo info = mCallback.mResponseInfo;
+        assertOKStatusCode(info);
+    }
+
+    @Test
+    public void testHttpEngine_setDnsOptions_requestSucceeds() {
+        DnsOptions options = new DnsOptions.Builder().build();
+        mEngine = mEngineBuilder.setDnsOptions(options).build();
+        UrlRequest.Builder builder =
+                mEngine.newUrlRequestBuilder(
+                        mTestServer.getSuccessUrl(), mCallback.getExecutor(), mCallback);
+        mRequest = builder.build();
+        mRequest.start();
+
+        mCallback.expectCallback(ResponseStep.ON_SUCCEEDED);
+        UrlResponseInfo info = mCallback.mResponseInfo;
+        assertOKStatusCode(info);
+    }
+
+    @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
new file mode 100644
index 0000000..dd4cf0d
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/NetworkExceptionTest.kt
@@ -0,0 +1,62 @@
+/*
+ * 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 androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+import kotlin.test.assertSame
+import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class NetworkExceptionTest {
+
+    @Test
+    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
new file mode 100644
index 0000000..4b7aa14
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/QuicExceptionTest.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http.cts
+
+import android.net.http.QuicException
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class QuicExceptionTest {
+
+    @Test
+    fun testQuicException_returnsInputParameters() {
+        val message = "failed"
+        val cause = Throwable("thrown")
+        val quicException =
+            object : QuicException(message, cause) {
+                override fun getErrorCode() = 0
+                override fun isImmediatelyRetryable() = false
+            }
+
+        assertEquals(message, quicException.message)
+        assertEquals(cause, quicException.cause)
+    }
+
+    // TODO: add test for QuicException triggered from HttpEngine
+}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt b/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt
new file mode 100644
index 0000000..0b02aa7
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/QuicOptionsTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.http.cts
+
+import android.net.http.QuicOptions
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import java.time.Duration
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class QuicOptionsTest {
+    @Test
+    fun testQuicOptions_defaultValues() {
+        val quicOptions = QuicOptions.Builder().build()
+        assertThat(quicOptions.allowedQuicHosts).isEmpty()
+        assertThat(quicOptions.handshakeUserAgent).isNull()
+        assertThat(quicOptions.idleConnectionTimeout).isNull()
+        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
new file mode 100644
index 0000000..422f4d5
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java
@@ -0,0 +1,412 @@
+/*
+ * 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.webkit.cts.CtsTestServer;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.base.Strings;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.URLEncoder;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+@RunWith(AndroidJUnit4.class)
+public class UrlRequestTest {
+    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);
+    }
+
+    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
new file mode 100644
index 0000000..38da9c5
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/UrlResponseInfoTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http.cts
+
+import android.content.Context
+import android.net.http.HttpEngine
+import android.net.http.cts.util.HttpCtsTestServer
+import android.net.http.cts.util.TestUrlRequestCallback
+import android.net.http.cts.util.TestUrlRequestCallback.ResponseStep
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class UrlResponseInfoTest {
+
+    @Test
+    fun testUrlResponseInfo_apisReturnCorrectInfo() {
+        // start the engine and send a request
+        val context: Context = ApplicationProvider.getApplicationContext()
+        val server = HttpCtsTestServer(context)
+        val httpEngine = HttpEngine.Builder(context).build()
+        val callback = TestUrlRequestCallback()
+        val url = server.successUrl
+        val request = httpEngine.newUrlRequestBuilder(url, callback.executor, callback).build()
+
+        request.start()
+        callback.expectCallback(ResponseStep.ON_SUCCEEDED)
+
+        val info = callback.mResponseInfo
+        assertFalse(info.headers.asList.isEmpty())
+        assertEquals(200, info.httpStatusCode)
+        assertTrue(info.receivedByteCount > 0)
+        assertEquals(url, info.url)
+        assertEquals(listOf(url), info.urlChain)
+        assertFalse(info.wasCached())
+
+        // TODO Current test server does not set these values. Uncomment when we use one that does.
+        // assertEquals("OK", info.httpStatusText)
+        // assertEquals("http/1.1", info.negotiatedProtocol)
+
+        // cronet defaults to port 0 when no proxy is specified.
+        // This is not a behaviour we want to enforce since null is reasonable too.
+        // assertEquals(":0", info.proxyServer)
+
+        server.shutdown()
+        httpEngine.shutdown()
+    }
+}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/util/HttpCtsTestServer.kt b/Cronet/tests/cts/src/android/net/http/cts/util/HttpCtsTestServer.kt
new file mode 100644
index 0000000..5196544
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/util/HttpCtsTestServer.kt
@@ -0,0 +1,57 @@
+/*
+ * 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
new file mode 100644
index 0000000..1e7333c
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/util/TestBidirectionalStreamCallback.java
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.http.cts.util;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeThat;
+import static org.junit.Assume.assumeTrue;
+
+import android.net.http.BidirectionalStream;
+import android.net.http.HeaderBlock;
+import android.net.http.HttpException;
+import android.net.http.UrlResponseInfo;
+import android.os.ConditionVariable;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * Callback that tracks information from different callbacks and has a method to block thread until
+ * the stream completes on another thread. Allows to cancel, block stream or throw an exception from
+ * an arbitrary step.
+ */
+public class TestBidirectionalStreamCallback implements BidirectionalStream.Callback {
+    private static final int TIMEOUT_MS = 12_000;
+    public UrlResponseInfo mResponseInfo;
+    public HttpException mError;
+
+    public ResponseStep mResponseStep = ResponseStep.NOTHING;
+
+    public boolean mOnErrorCalled;
+    public boolean mOnCanceledCalled;
+
+    public int mHttpResponseDataLength;
+    public String mResponseAsString = "";
+
+    public HeaderBlock mTrailers;
+
+    private static final int READ_BUFFER_SIZE = 32 * 1024;
+
+    // When false, the consumer is responsible for all calls into the stream
+    // that advance it.
+    private boolean mAutoAdvance = true;
+
+    // Conditionally fail on certain steps.
+    private FailureType mFailureType = FailureType.NONE;
+    private ResponseStep mFailureStep = ResponseStep.NOTHING;
+
+    // Signals when the stream is done either successfully or not.
+    private final ConditionVariable mDone = new ConditionVariable();
+
+    // Signaled on each step when mAutoAdvance is false.
+    private final ConditionVariable mReadStepBlock = new ConditionVariable();
+    private final ConditionVariable mWriteStepBlock = new ConditionVariable();
+
+    // Executor Service for Cronet callbacks.
+    private final ExecutorService mExecutorService =
+            Executors.newSingleThreadExecutor(new ExecutorThreadFactory());
+    private Thread mExecutorThread;
+
+    // position() of ByteBuffer prior to read() call.
+    private int mBufferPositionBeforeRead;
+
+    // Data to write.
+    private final ArrayList<WriteBuffer> mWriteBuffers = new ArrayList<WriteBuffer>();
+
+    // Buffers that we yet to receive the corresponding onWriteCompleted callback.
+    private final ArrayList<WriteBuffer> mWriteBuffersToBeAcked = new ArrayList<WriteBuffer>();
+
+    // Whether to use a direct executor.
+    private final boolean mUseDirectExecutor;
+    private final DirectExecutor mDirectExecutor;
+
+    private class ExecutorThreadFactory implements ThreadFactory {
+        @Override
+        public Thread newThread(Runnable r) {
+            mExecutorThread = new Thread(r);
+            return mExecutorThread;
+        }
+    }
+
+    private static class WriteBuffer {
+        final ByteBuffer mBuffer;
+        final boolean mFlush;
+
+        WriteBuffer(ByteBuffer buffer, boolean flush) {
+            mBuffer = buffer;
+            mFlush = flush;
+        }
+    }
+
+    private static class DirectExecutor implements Executor {
+        @Override
+        public void execute(Runnable task) {
+            task.run();
+        }
+    }
+
+    public enum ResponseStep {
+        NOTHING,
+        ON_STREAM_READY,
+        ON_RESPONSE_STARTED,
+        ON_READ_COMPLETED,
+        ON_WRITE_COMPLETED,
+        ON_TRAILERS,
+        ON_CANCELED,
+        ON_FAILED,
+        ON_SUCCEEDED,
+    }
+
+    public enum FailureType {
+        NONE,
+        CANCEL_SYNC,
+        CANCEL_ASYNC,
+        // Same as above, but continues to advance the stream after posting
+        // the cancellation task.
+        CANCEL_ASYNC_WITHOUT_PAUSE,
+        THROW_SYNC
+    }
+
+    private boolean isTerminalCallback(ResponseStep step) {
+        switch (step) {
+            case ON_SUCCEEDED:
+            case ON_CANCELED:
+            case ON_FAILED:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    public TestBidirectionalStreamCallback() {
+        mUseDirectExecutor = false;
+        mDirectExecutor = null;
+    }
+
+    public TestBidirectionalStreamCallback(boolean useDirectExecutor) {
+        mUseDirectExecutor = useDirectExecutor;
+        mDirectExecutor = new DirectExecutor();
+    }
+
+    public void setAutoAdvance(boolean autoAdvance) {
+        mAutoAdvance = autoAdvance;
+    }
+
+    public void setFailure(FailureType failureType, ResponseStep failureStep) {
+        mFailureStep = failureStep;
+        mFailureType = failureType;
+    }
+
+    public boolean blockForDone() {
+        return mDone.block(TIMEOUT_MS);
+    }
+
+    /**
+     * Waits for a terminal callback to complete execution before failing if the callback is not the
+     * expected one
+     *
+     * @param expectedStep the expected callback step
+     */
+    public void expectCallback(ResponseStep expectedStep) {
+        if (isTerminalCallback(expectedStep)) {
+            assertTrue(String.format(
+                            "Request timed out. Expected %s callback. Current callback is %s",
+                            expectedStep, mResponseStep),
+                    blockForDone());
+        }
+        assertSame(expectedStep, mResponseStep);
+    }
+
+    /**
+     * Waits for a terminal callback to complete execution before skipping the test if the callback
+     * is not the expected one
+     *
+     * @param expectedStep the expected callback step
+     */
+    public void assumeCallback(ResponseStep expectedStep) {
+        if (isTerminalCallback(expectedStep)) {
+            assumeTrue(
+                    String.format(
+                            "Request timed out. Expected %s callback. Current callback is %s",
+                            expectedStep, mResponseStep),
+                    blockForDone());
+        }
+        assumeThat(expectedStep, equalTo(mResponseStep));
+    }
+
+    public void waitForNextReadStep() {
+        mReadStepBlock.block();
+        mReadStepBlock.close();
+    }
+
+    public void waitForNextWriteStep() {
+        mWriteStepBlock.block();
+        mWriteStepBlock.close();
+    }
+
+    public Executor getExecutor() {
+        if (mUseDirectExecutor) {
+            return mDirectExecutor;
+        }
+        return mExecutorService;
+    }
+
+    public void shutdownExecutor() {
+        if (mUseDirectExecutor) {
+            throw new UnsupportedOperationException("DirectExecutor doesn't support shutdown");
+        }
+        mExecutorService.shutdown();
+    }
+
+    public void addWriteData(byte[] data) {
+        addWriteData(data, true);
+    }
+
+    public void addWriteData(byte[] data, boolean flush) {
+        ByteBuffer writeBuffer = ByteBuffer.allocateDirect(data.length);
+        writeBuffer.put(data);
+        writeBuffer.flip();
+        mWriteBuffers.add(new WriteBuffer(writeBuffer, flush));
+        mWriteBuffersToBeAcked.add(new WriteBuffer(writeBuffer, flush));
+    }
+
+    @Override
+    public void onStreamReady(BidirectionalStream stream) {
+        checkOnValidThread();
+        assertFalse(stream.isDone());
+        assertEquals(ResponseStep.NOTHING, mResponseStep);
+        assertNull(mError);
+        mResponseStep = ResponseStep.ON_STREAM_READY;
+        if (maybeThrowCancelOrPause(stream, mWriteStepBlock)) {
+            return;
+        }
+        startNextWrite(stream);
+    }
+
+    @Override
+    public void onResponseHeadersReceived(BidirectionalStream stream, UrlResponseInfo info) {
+        checkOnValidThread();
+        assertFalse(stream.isDone());
+        assertTrue(
+                mResponseStep == ResponseStep.NOTHING
+                        || mResponseStep == ResponseStep.ON_STREAM_READY
+                        || mResponseStep == ResponseStep.ON_WRITE_COMPLETED);
+        assertNull(mError);
+
+        mResponseStep = ResponseStep.ON_RESPONSE_STARTED;
+        mResponseInfo = info;
+        if (maybeThrowCancelOrPause(stream, mReadStepBlock)) {
+            return;
+        }
+        startNextRead(stream);
+    }
+
+    @Override
+    public void onReadCompleted(
+            BidirectionalStream stream,
+            UrlResponseInfo info,
+            ByteBuffer byteBuffer,
+            boolean endOfStream) {
+        checkOnValidThread();
+        assertFalse(stream.isDone());
+        assertTrue(
+                mResponseStep == ResponseStep.ON_RESPONSE_STARTED
+                        || mResponseStep == ResponseStep.ON_READ_COMPLETED
+                        || mResponseStep == ResponseStep.ON_WRITE_COMPLETED
+                        || mResponseStep == ResponseStep.ON_TRAILERS);
+        assertNull(mError);
+
+        mResponseStep = ResponseStep.ON_READ_COMPLETED;
+        mResponseInfo = info;
+
+        final int bytesRead = byteBuffer.position() - mBufferPositionBeforeRead;
+        mHttpResponseDataLength += bytesRead;
+        final byte[] lastDataReceivedAsBytes = new byte[bytesRead];
+        // Rewind byteBuffer.position() to pre-read() position.
+        byteBuffer.position(mBufferPositionBeforeRead);
+        // This restores byteBuffer.position() to its value on entrance to
+        // this function.
+        byteBuffer.get(lastDataReceivedAsBytes);
+
+        mResponseAsString += new String(lastDataReceivedAsBytes);
+
+        if (maybeThrowCancelOrPause(stream, mReadStepBlock)) {
+            return;
+        }
+        // Do not read if EOF has been reached.
+        if (!endOfStream) {
+            startNextRead(stream);
+        }
+    }
+
+    @Override
+    public void onWriteCompleted(
+            BidirectionalStream stream,
+            UrlResponseInfo info,
+            ByteBuffer buffer,
+            boolean endOfStream) {
+        checkOnValidThread();
+        assertFalse(stream.isDone());
+        assertNull(mError);
+        mResponseStep = ResponseStep.ON_WRITE_COMPLETED;
+        mResponseInfo = info;
+        if (!mWriteBuffersToBeAcked.isEmpty()) {
+            assertEquals(buffer, mWriteBuffersToBeAcked.get(0).mBuffer);
+            mWriteBuffersToBeAcked.remove(0);
+        }
+        if (maybeThrowCancelOrPause(stream, mWriteStepBlock)) {
+            return;
+        }
+        startNextWrite(stream);
+    }
+
+    @Override
+    public void onResponseTrailersReceived(
+            BidirectionalStream stream,
+            UrlResponseInfo info,
+            HeaderBlock trailers) {
+        checkOnValidThread();
+        assertFalse(stream.isDone());
+        assertNull(mError);
+        mResponseStep = ResponseStep.ON_TRAILERS;
+        mResponseInfo = info;
+        mTrailers = trailers;
+        if (maybeThrowCancelOrPause(stream, mReadStepBlock)) {
+            return;
+        }
+    }
+
+    @Override
+    public void onSucceeded(BidirectionalStream stream, UrlResponseInfo info) {
+        checkOnValidThread();
+        assertTrue(stream.isDone());
+        assertTrue(
+                mResponseStep == ResponseStep.ON_RESPONSE_STARTED
+                        || mResponseStep == ResponseStep.ON_READ_COMPLETED
+                        || mResponseStep == ResponseStep.ON_WRITE_COMPLETED
+                        || mResponseStep == ResponseStep.ON_TRAILERS);
+        assertFalse(mOnErrorCalled);
+        assertFalse(mOnCanceledCalled);
+        assertNull(mError);
+        assertEquals(0, mWriteBuffers.size());
+        assertEquals(0, mWriteBuffersToBeAcked.size());
+
+        mResponseStep = ResponseStep.ON_SUCCEEDED;
+        mResponseInfo = info;
+        openDone();
+        maybeThrowCancelOrPause(stream, mReadStepBlock);
+    }
+
+    @Override
+    public void onFailed(BidirectionalStream stream, UrlResponseInfo info, HttpException error) {
+        checkOnValidThread();
+        assertTrue(stream.isDone());
+        // Shouldn't happen after success.
+        assertTrue(mResponseStep != ResponseStep.ON_SUCCEEDED);
+        // Should happen at most once for a single stream.
+        assertFalse(mOnErrorCalled);
+        assertFalse(mOnCanceledCalled);
+        assertNull(mError);
+        mResponseStep = ResponseStep.ON_FAILED;
+        mResponseInfo = info;
+
+        mOnErrorCalled = true;
+        mError = error;
+        openDone();
+        maybeThrowCancelOrPause(stream, mReadStepBlock);
+    }
+
+    @Override
+    public void onCanceled(BidirectionalStream stream, UrlResponseInfo info) {
+        checkOnValidThread();
+        assertTrue(stream.isDone());
+        // Should happen at most once for a single stream.
+        assertFalse(mOnCanceledCalled);
+        assertFalse(mOnErrorCalled);
+        assertNull(mError);
+        mResponseStep = ResponseStep.ON_CANCELED;
+        mResponseInfo = info;
+
+        mOnCanceledCalled = true;
+        openDone();
+        maybeThrowCancelOrPause(stream, mReadStepBlock);
+    }
+
+    public void startNextRead(BidirectionalStream stream) {
+        startNextRead(stream, ByteBuffer.allocateDirect(READ_BUFFER_SIZE));
+    }
+
+    public void startNextRead(BidirectionalStream stream, ByteBuffer buffer) {
+        mBufferPositionBeforeRead = buffer.position();
+        stream.read(buffer);
+    }
+
+    public void startNextWrite(BidirectionalStream stream) {
+        if (!mWriteBuffers.isEmpty()) {
+            Iterator<WriteBuffer> iterator = mWriteBuffers.iterator();
+            while (iterator.hasNext()) {
+                WriteBuffer b = iterator.next();
+                stream.write(b.mBuffer, !iterator.hasNext());
+                iterator.remove();
+                if (b.mFlush) {
+                    stream.flush();
+                    break;
+                }
+            }
+        }
+    }
+
+    public boolean isDone() {
+        // It's not mentioned by the Android docs, but block(0) seems to block
+        // indefinitely, so have to block for one millisecond to get state
+        // without blocking.
+        return mDone.block(1);
+    }
+
+    /** Returns the number of pending Writes. */
+    public int numPendingWrites() {
+        return mWriteBuffers.size();
+    }
+
+    protected void openDone() {
+        mDone.open();
+    }
+
+    /** Returns {@code false} if the callback should continue to advance the stream. */
+    private boolean maybeThrowCancelOrPause(
+            final BidirectionalStream stream, ConditionVariable stepBlock) {
+        if (mResponseStep != mFailureStep || mFailureType == FailureType.NONE) {
+            if (!mAutoAdvance) {
+                stepBlock.open();
+                return true;
+            }
+            return false;
+        }
+
+        if (mFailureType == FailureType.THROW_SYNC) {
+            throw new IllegalStateException("Callback Exception.");
+        }
+        Runnable task =
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        stream.cancel();
+                    }
+                };
+        if (mFailureType == FailureType.CANCEL_ASYNC
+                || mFailureType == FailureType.CANCEL_ASYNC_WITHOUT_PAUSE) {
+            getExecutor().execute(task);
+        } else {
+            task.run();
+        }
+        return mFailureType != FailureType.CANCEL_ASYNC_WITHOUT_PAUSE;
+    }
+
+    /** Checks whether callback methods are invoked on the correct thread. */
+    private void checkOnValidThread() {
+        if (!mUseDirectExecutor) {
+            assertEquals(mExecutorThread, Thread.currentThread());
+        }
+    }
+}
diff --git a/Cronet/tests/cts/src/android/net/http/cts/util/TestStatusListener.kt b/Cronet/tests/cts/src/android/net/http/cts/util/TestStatusListener.kt
new file mode 100644
index 0000000..3a4486f
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/util/TestStatusListener.kt
@@ -0,0 +1,38 @@
+/*
+ * 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/org/chromium/net/test/util/TestUrlRequestCallback.java b/Cronet/tests/cts/src/android/net/http/cts/util/TestUrlRequestCallback.java
similarity index 88%
rename from Cronet/tests/cts/src/org/chromium/net/test/util/TestUrlRequestCallback.java
rename to Cronet/tests/cts/src/android/net/http/cts/util/TestUrlRequestCallback.java
index 3c7c001..28443b7 100644
--- a/Cronet/tests/cts/src/org/chromium/net/test/util/TestUrlRequestCallback.java
+++ b/Cronet/tests/cts/src/android/net/http/cts/util/TestUrlRequestCallback.java
@@ -14,25 +14,30 @@
  * limitations under the License.
  */
 
-package org.chromium.net.test.util;
+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 org.chromium.net.CallbackException;
-import org.chromium.net.CronetException;
-import org.chromium.net.InlineExecutionProhibitedException;
-import org.chromium.net.UrlRequest;
-import org.chromium.net.UrlResponseInfo;
-
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.concurrent.ExecutorService;
@@ -45,12 +50,12 @@
  * method to block thread until the request completes on another thread.
  * Allows us to cancel, block request or throw an exception from an arbitrary step.
  */
-public class TestUrlRequestCallback extends UrlRequest.Callback {
+public class TestUrlRequestCallback implements UrlRequest.Callback {
     private static final int TIMEOUT_MS = 12_000;
     public ArrayList<UrlResponseInfo> mRedirectResponseInfoList = new ArrayList<>();
     public ArrayList<String> mRedirectUrlList = new ArrayList<>();
     public UrlResponseInfo mResponseInfo;
-    public CronetException mError;
+    public HttpException mError;
 
     public ResponseStep mResponseStep = ResponseStep.NOTHING;
 
@@ -89,7 +94,7 @@
     // Signaled on each step when mAutoAdvance is false.
     private final ConditionVariable mStepBlock = new ConditionVariable();
 
-    // Executor Service for Cronet callbacks.
+    // Executor Service for Http callbacks.
     private final ExecutorService mExecutorService;
     private Thread mExecutorThread;
 
@@ -233,6 +238,19 @@
     }
 
     /**
+     * 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.
      */
@@ -273,8 +291,9 @@
             UrlRequest request, UrlResponseInfo info, String newLocationUrl) {
         checkExecutorThread();
         assertFalse(request.isDone());
-        assertTrue(mResponseStep == ResponseStep.NOTHING
-                || mResponseStep == ResponseStep.ON_RECEIVED_REDIRECT);
+        assertThat(mResponseStep, anyOf(
+                equalTo(ResponseStep.NOTHING),
+                equalTo(ResponseStep.ON_RECEIVED_REDIRECT)));
         assertNull(mError);
 
         mResponseStep = ResponseStep.ON_RECEIVED_REDIRECT;
@@ -291,8 +310,9 @@
     public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
         checkExecutorThread();
         assertFalse(request.isDone());
-        assertTrue(mResponseStep == ResponseStep.NOTHING
-                || mResponseStep == ResponseStep.ON_RECEIVED_REDIRECT);
+        assertThat(mResponseStep, anyOf(
+                equalTo(ResponseStep.NOTHING),
+                equalTo(ResponseStep.ON_RECEIVED_REDIRECT)));
         assertNull(mError);
 
         mResponseStep = ResponseStep.ON_RESPONSE_STARTED;
@@ -307,8 +327,9 @@
     public void onReadCompleted(UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) {
         checkExecutorThread();
         assertFalse(request.isDone());
-        assertTrue(mResponseStep == ResponseStep.ON_RESPONSE_STARTED
-                || mResponseStep == ResponseStep.ON_READ_COMPLETED);
+        assertThat(mResponseStep, anyOf(
+                equalTo(ResponseStep.ON_RESPONSE_STARTED),
+                equalTo(ResponseStep.ON_READ_COMPLETED)));
         assertNull(mError);
 
         mResponseStep = ResponseStep.ON_READ_COMPLETED;
@@ -334,8 +355,9 @@
     public void onSucceeded(UrlRequest request, UrlResponseInfo info) {
         checkExecutorThread();
         assertTrue(request.isDone());
-        assertTrue(mResponseStep == ResponseStep.ON_RESPONSE_STARTED
-                || mResponseStep == ResponseStep.ON_READ_COMPLETED);
+        assertThat(mResponseStep, anyOf(
+                equalTo(ResponseStep.ON_RESPONSE_STARTED),
+                equalTo(ResponseStep.ON_READ_COMPLETED)));
         assertFalse(mOnErrorCalled);
         assertFalse(mOnCanceledCalled);
         assertNull(mError);
@@ -349,7 +371,7 @@
     }
 
     @Override
-    public void onFailed(UrlRequest request, UrlResponseInfo info, CronetException error) {
+    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) {
@@ -358,7 +380,7 @@
         checkExecutorThread();
         assertTrue(request.isDone());
         // Shouldn't happen after success.
-        assertTrue(mResponseStep != ResponseStep.ON_SUCCEEDED);
+        assertNotEquals(ResponseStep.ON_SUCCEEDED, mResponseStep);
         // Should happen at most once for a single request.
         assertFalse(mOnErrorCalled);
         assertFalse(mOnCanceledCalled);
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
new file mode 100644
index 0000000..7fc005a
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/util/TestUtils.kt
@@ -0,0 +1,40 @@
+/*
+ * 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
new file mode 100644
index 0000000..3b90fa0
--- /dev/null
+++ b/Cronet/tests/cts/src/android/net/http/cts/util/UploadDataProviders.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 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/cts/src/org/chromium/net/test/CronetUrlRequestTest.java b/Cronet/tests/cts/src/org/chromium/net/test/CronetUrlRequestTest.java
deleted file mode 100644
index 7dd9a9a..0000000
--- a/Cronet/tests/cts/src/org/chromium/net/test/CronetUrlRequestTest.java
+++ /dev/null
@@ -1,91 +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 org.chromium.net.test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.net.ConnectivityManager;
-
-import androidx.annotation.NonNull;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.chromium.net.CronetEngine;
-import org.chromium.net.UrlRequest;
-import org.chromium.net.UrlResponseInfo;
-import org.chromium.net.test.util.TestUrlRequestCallback;
-import org.chromium.net.test.util.TestUrlRequestCallback.ResponseStep;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Random;
-
-@RunWith(AndroidJUnit4.class)
-public class CronetUrlRequestTest {
-    private static final String TAG = CronetUrlRequestTest.class.getSimpleName();
-    private static final String HTTPS_PREFIX = "https://";
-
-    private final String[] mTestDomains = {"www.google.com", "www.android.com"};
-    @NonNull private CronetEngine mCronetEngine;
-    @NonNull private ConnectivityManager mCm;
-
-    @Before
-    public void setUp() throws Exception {
-        Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
-        CronetEngine.Builder builder = new CronetEngine.Builder(context);
-        builder.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY, 100 * 1024)
-                .enableHttp2(true)
-                // .enableBrotli(true)
-                .enableQuic(true);
-        mCronetEngine = builder.build();
-    }
-
-    private static void assertGreaterThan(String msg, int first, int second) {
-        assertTrue(msg + " Excepted " + first + " to be greater than " + second, first > second);
-    }
-
-    private void assertHasTestableNetworks() {
-        assertNotNull("This test requires a working Internet connection", mCm.getActiveNetwork());
-    }
-
-    private String getRandomDomain() {
-        int index = (new Random()).nextInt(mTestDomains.length);
-        return mTestDomains[index];
-    }
-
-    @Test
-    public void testUrlRequestGet_CompletesSuccessfully() throws Exception {
-        assertHasTestableNetworks();
-        String url = HTTPS_PREFIX + getRandomDomain();
-        TestUrlRequestCallback callback = new TestUrlRequestCallback();
-        UrlRequest.Builder builder = mCronetEngine.newUrlRequestBuilder(url, callback,
-                callback.getExecutor());
-        builder.build().start();
-
-        callback.expectCallback(ResponseStep.ON_SUCCEEDED);
-
-        UrlResponseInfo info = callback.mResponseInfo;
-        assertEquals("Unexpected http status code from " + url + ".", 200,
-                info.getHttpStatusCode());
-        assertGreaterThan(
-                "Received byte from " + url + " is 0.", (int) info.getReceivedByteCount(), 0);
-    }
-}
diff --git a/Cronet/tests/mts/Android.bp b/Cronet/tests/mts/Android.bp
new file mode 100644
index 0000000..4e4251c
--- /dev/null
+++ b/Cronet/tests/mts/Android.bp
@@ -0,0 +1,61 @@
+// 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 {
+    // 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_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_tests__testing"
+     ],
+     test_suites: [
+         "general-tests",
+         "mts-tethering",
+     ],
+}
+
diff --git a/Cronet/tests/mts/AndroidManifest.xml b/Cronet/tests/mts/AndroidManifest.xml
new file mode 100644
index 0000000..f597134
--- /dev/null
+++ b/Cronet/tests/mts/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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.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
new file mode 100644
index 0000000..0d780a1
--- /dev/null
+++ b/Cronet/tests/mts/AndroidTest.xml
@@ -0,0 +1,39 @@
+<?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>
+
+    <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" />
+        <option name="hidden-api-checks" value="false"/>
+    </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>
\ No newline at end of file
diff --git a/Cronet/tests/mts/jarjar_excludes.txt b/Cronet/tests/mts/jarjar_excludes.txt
new file mode 100644
index 0000000..a3e86de
--- /dev/null
+++ b/Cronet/tests/mts/jarjar_excludes.txt
@@ -0,0 +1,15 @@
+# It's prohibited to jarjar androidx packages
+androidx\..+
+# 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(\$.+)?
+
+# 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(\$.+)?
\ 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
new file mode 100644
index 0000000..d44c36f
--- /dev/null
+++ b/Cronet/tests/mts/res/xml/network_security_config.xml
@@ -0,0 +1,34 @@
+<?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">
+        <!-- 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/Cronet/tools/import/copy.bara.sky b/Cronet/tools/import/copy.bara.sky
new file mode 100644
index 0000000..5372a4d
--- /dev/null
+++ b/Cronet/tools/import/copy.bara.sky
@@ -0,0 +1,119 @@
+# Copyright 2023 Google Inc. All rights reserved.
+#
+# 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.
+
+common_excludes = [
+    # Exclude all Android build files
+    "**/Android.bp",
+    "**/Android.mk",
+
+    # Exclude existing *OWNERS files
+    "**/*OWNERS",
+    "**/.git/**",
+    "**/.gitignore",
+]
+
+cronet_origin_files = glob(
+    include = [
+        "base/**",
+        "build/**",
+        "build/buildflag.h",
+        "chrome/VERSION",
+        "components/cronet/**",
+        "components/metrics/**",
+        "components/nacl/**",
+        "components/prefs/**",
+        "crypto/**",
+        "ipc/**",
+        "net/**",
+        # Note: Only used for tests.
+        "testing/**",
+        "url/**",
+        "LICENSE",
+    ],
+    exclude = common_excludes + [
+        # Per aosp/2367109
+        "build/android/CheckInstallApk-debug.apk",
+        "build/android/unused_resources/**",
+        "build/linux/**",
+
+        # Per aosp/2374766
+        "components/cronet/ios/**",
+        "components/cronet/native/**",
+
+        # Per aosp/2399270
+        "testing/buildbot/**",
+
+        # Exclude all third-party directories. Those are specified explicitly
+        # below, so no dependency can accidentally creep in.
+        "**/third_party/**",
+    ],
+) + glob(
+    # Explicitly include third-party dependencies.
+    # Note: some third-party dependencies include a third_party folder within
+    # them. So far, this has not become a problem.
+    include = [
+        "base/third_party/cityhash/**",
+        "base/third_party/cityhash_v103/**",
+        "base/third_party/double_conversion/**",
+        "base/third_party/dynamic_annotations/**",
+        "base/third_party/icu/**",
+        "base/third_party/nspr/**",
+        "base/third_party/superfasthash/**",
+        "base/third_party/valgrind/**",
+        "buildtools/third_party/libc++/**",
+        "buildtools/third_party/libc++abi/**",
+        # Note: Only used for tests.
+        "net/third_party/nist-pkits/**",
+        "net/third_party/quiche/**",
+        "net/third_party/uri_template/**",
+        "third_party/abseil-cpp/**",
+        "third_party/android_ndk/sources/android/cpufeatures/**",
+        "third_party/ashmem/**",
+        "third_party/boringssl/**",
+        "third_party/brotli/**",
+        # Note: Only used for tests.
+        "third_party/ced/**",
+        # Note: Only used for tests.
+        "third_party/googletest/**",
+        "third_party/icu/**",
+        "third_party/libevent/**",
+        # Note: Only used for tests.
+        "third_party/libxml/**",
+        # Note: Only used for tests.
+        "third_party/lss/**",
+        "third_party/metrics_proto/**",
+        "third_party/modp_b64/**",
+        "third_party/protobuf/**",
+        # Note: Only used for tests.
+        "third_party/quic_trace/**",
+        # Note: Cronet currently uses Android's zlib
+        # "third_party/zlib/**",
+        "url/third_party/mozilla/**",
+    ],
+    exclude = common_excludes,
+)
+
+core.workflow(
+    name = "import_cronet",
+    authoring = authoring.overwrite("Cronet Mainline Eng <cronet-mainline-eng+copybara@google.com>"),
+    # Origin folder is specified via source_ref argument, see import_cronet.sh
+    origin = folder.origin(),
+    origin_files = cronet_origin_files,
+    destination = git.destination(
+        # The destination URL is set by the invoking script.
+        url = "overwritten/by/script",
+        push = "upstream-import",
+    ),
+    mode = "SQUASH",
+)
diff --git a/Cronet/tools/import/import_cronet.sh b/Cronet/tools/import/import_cronet.sh
new file mode 100755
index 0000000..0f04af7
--- /dev/null
+++ b/Cronet/tools/import/import_cronet.sh
@@ -0,0 +1,146 @@
+#!/bin/bash
+
+# Copyright 2023 Google Inc. All rights reserved.
+#
+# 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.
+
+# Script to invoke copybara locally to import Cronet into Android.
+# Inputs:
+#  Environment:
+#   ANDROID_BUILD_TOP: path the root of the current Android directory.
+#  Arguments:
+#   -l rev: The last revision that was imported.
+#  Optional Arguments:
+#   -n rev: The new revision to import.
+#   -f: Force copybara to ignore a failure to find the last imported revision.
+
+set -e -x
+
+OPTSTRING=fl:n:
+
+usage() {
+    cat <<EOF
+Usage: import_cronet.sh -n new-rev [-l last-rev] [-f]
+EOF
+    exit 1
+}
+
+COPYBARA_FOLDER_ORIGIN="/tmp/copybara-origin"
+
+#######################################
+# Create local upstream-import branch in external/cronet.
+# Globals:
+#   ANDROID_BUILD_TOP
+# Arguments:
+#   none
+#######################################
+setup_upstream_import_branch() {
+    local git_dir="${ANDROID_BUILD_TOP}/external/cronet"
+
+    (cd "${git_dir}" && git fetch aosp upstream-import:upstream-import)
+}
+
+#######################################
+# Setup folder.origin for copybara inside /tmp
+# Globals:
+#   COPYBARA_FOLDER_ORIGIN
+# Arguments:
+#   new_rev, string
+#######################################
+setup_folder_origin() (
+    local _new_rev=$1
+    mkdir -p "${COPYBARA_FOLDER_ORIGIN}"
+    cd "${COPYBARA_FOLDER_ORIGIN}"
+
+    if [ -d src ]; then
+        (cd src && git fetch --tags && git checkout "${_new_rev}")
+    else
+        # For this to work _new_rev must be a branch or a tag.
+        git clone --depth=1 --branch "${_new_rev}" https://chromium.googlesource.com/chromium/src.git
+    fi
+
+
+    cat <<EOF >.gclient
+solutions = [
+  {
+    "name": "src",
+    "url": "https://chromium.googlesource.com/chromium/src.git",
+    "managed": False,
+    "custom_deps": {},
+    "custom_vars": {},
+  },
+]
+target_os = ["android"]
+EOF
+    cd src
+    # Set appropriate gclient flags to speed up syncing.
+    gclient sync \
+        --no-history \
+        --shallow \
+        --delete_unversioned_trees
+)
+
+#######################################
+# Runs the copybara import of Chromium
+# Globals:
+#   ANDROID_BUILD_TOP
+#   COPYBARA_FOLDER_ORIGIN
+# Arguments:
+#   last_rev, string or empty
+#   force, string or empty
+#######################################
+do_run_copybara() {
+    local _last_rev=$1
+    local _force=$2
+
+    local -a flags
+    flags+=(--git-destination-url="file://${ANDROID_BUILD_TOP}/external/cronet")
+    flags+=(--repo-timeout 3m)
+
+    # buildtools/third_party/libc++ contains an invalid symlink
+    flags+=(--folder-origin-ignore-invalid-symlinks)
+    flags+=(--git-no-verify)
+
+    if [ ! -z "${_force}" ]; then
+        flags+=(--force)
+    fi
+
+    if [ ! -z "${_last_rev}" ]; then
+        flags+=(--last-rev "${_last_rev}")
+    fi
+
+    /google/bin/releases/copybara/public/copybara/copybara \
+        "${flags[@]}" \
+        "${ANDROID_BUILD_TOP}/packages/modules/Connectivity/Cronet/tools/import/copy.bara.sky" \
+        import_cronet "${COPYBARA_FOLDER_ORIGIN}/src"
+}
+
+while getopts $OPTSTRING opt; do
+    case "${opt}" in
+        f) force=true ;;
+        l) last_rev="${OPTARG}" ;;
+        n) new_rev="${OPTARG}" ;;
+        ?) usage ;;
+        *) echo "'${opt}' '${OPTARG}'"
+    esac
+done
+
+if [ -z "${new_rev}" ]; then
+    echo "-n argument required"
+    usage
+fi
+
+setup_upstream_import_branch
+setup_folder_origin "${new_rev}"
+do_run_copybara "${last_rev}" "${force}"
+
diff --git a/OWNERS_core_networking b/OWNERS_core_networking
index 3a08422..6d17476 100644
--- a/OWNERS_core_networking
+++ b/OWNERS_core_networking
@@ -1,4 +1,3 @@
-chenbruce@google.com
 chiachangwang@google.com
 cken@google.com
 huangaaron@google.com
@@ -19,3 +18,4 @@
 waynema@google.com
 xiaom@google.com
 yumike@google.com
+yuyanghuang@google.com
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 700a085..d2f6d6a 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -58,6 +58,17 @@
       ]
     },
     {
+      "name": "CtsNetTestCasesMaxTargetSdk33",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        }
+      ]
+    },
+    {
       "name": "bpf_existence_test"
     },
     {
@@ -83,6 +94,16 @@
     },
     {
       "name": "FrameworksNetIntegrationTests"
+    },
+    // Runs both NetHttpTests and CtsNetHttpTestCases
+    {
+      "name": "NetHttpCoverageTests",
+      "options": [
+        {
+          // These sometimes take longer than 1 min which is the presubmit timeout
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ]
     }
   ],
   "postsubmit": [
@@ -135,6 +156,17 @@
         }
       ]
     },
+    {
+      "name": "CtsNetTestCasesMaxTargetSdk33[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+      "options": [
+        {
+          "exclude-annotation": "com.android.testutils.SkipPresubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.RequiresDevice"
+        }
+      ]
+    },
     // Test with APK modules only, in cases where APEX is not supported, or the other modules
     // were simply not updated
     {
@@ -183,6 +215,15 @@
     },
     {
       "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": [
+        {
+          // These sometimes take longer than 1 min which is the presubmit timeout
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ]
     }
   ],
   "mainline-postsubmit": [
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 829e66a..4478b1e 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -34,6 +34,7 @@
     // Libraries not including Tethering's own framework-tethering (different flavors of that one
     // are needed depending on the build rule)
     libs: [
+        "connectivity-internal-api-util",
         "framework-configinfrastructure",
         "framework-connectivity.stubs.module_lib",
         "framework-connectivity-t.stubs.module_lib",
@@ -60,9 +61,13 @@
         "modules-utils-build",
         "modules-utils-statemachine",
         "networkstack-client",
+        // AIDL tetheroffload implementation
+        "android.hardware.tetheroffload-V1-java",
+        // HIDL tetheroffload implementation
         "android.hardware.tetheroffload.config-V1.0-java",
         "android.hardware.tetheroffload.control-V1.0-java",
         "android.hardware.tetheroffload.control-V1.1-java",
+        "android.hidl.manager-V1.2-java",
         "net-utils-framework-common",
         "net-utils-device-common",
         "net-utils-device-common-bpf",
@@ -114,7 +119,6 @@
     name: "libcom_android_networkstack_tethering_util_jni",
     sdk_version: "30",
     apex_available: [
-        "//apex_available:platform", // Used by InProcessTethering
         "com.android.tethering",
     ],
     min_sdk_version: "30",
@@ -183,24 +187,6 @@
     lint: { strict_updatability_linting: true },
 }
 
-// Non-updatable tethering running in the system server process for devices not using the module
-android_app {
-    name: "InProcessTethering",
-    defaults: [
-        "TetheringAppDefaults",
-        "TetheringApiLevel",
-        "ConnectivityNextEnableDefaults",
-        "TetheringReleaseTargetSdk"
-    ],
-    static_libs: ["TetheringApiCurrentLib"],
-    certificate: "platform",
-    manifest: "AndroidManifest_InProcess.xml",
-    // InProcessTethering is a replacement for Tethering
-    overrides: ["Tethering"],
-    apex_available: ["com.android.tethering"],
-    lint: { strict_updatability_linting: true },
-}
-
 // Updatable tethering packaged for finalized API
 android_app {
     name: "Tethering",
@@ -209,11 +195,7 @@
     certificate: "networkstack",
     manifest: "AndroidManifest.xml",
     use_embedded_native_libs: true,
-    // The network stack *must* be included to ensure security of the device
-    required: [
-        "NetworkStack",
-        "privapp_allowlist_com.android.tethering",
-    ],
+    privapp_allowlist: ":privapp_allowlist_com.android.tethering",
     apex_available: ["com.android.tethering"],
     lint: { strict_updatability_linting: true },
 }
@@ -229,11 +211,7 @@
     certificate: "networkstack",
     manifest: "AndroidManifest.xml",
     use_embedded_native_libs: true,
-    // The network stack *must* be included to ensure security of the device
-    required: [
-        "NetworkStackNext",
-        "privapp_allowlist_com.android.tethering",
-    ],
+    privapp_allowlist: ":privapp_allowlist_com.android.tethering",
     apex_available: ["com.android.tethering"],
     lint: { strict_updatability_linting: true },
 }
@@ -245,6 +223,9 @@
         // e.g. *classpath_fragments.
         "com.android.tethering",
     ],
+    native_shared_libs: [
+        "libnetd_updatable",
+    ],
 }
 
 java_library_static {
diff --git a/Tethering/AndroidManifest.xml b/Tethering/AndroidManifest.xml
index b832e16..6a363b0 100644
--- a/Tethering/AndroidManifest.xml
+++ b/Tethering/AndroidManifest.xml
@@ -43,6 +43,7 @@
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
     <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
 
+    <!-- Sending non-protected broadcast from system uid is not allowed. -->
     <protected-broadcast android:name="com.android.server.connectivity.tethering.DISABLE_TETHERING" />
 
     <application
diff --git a/Tethering/AndroidManifest_InProcess.xml b/Tethering/AndroidManifest_InProcess.xml
deleted file mode 100644
index b1f1240..0000000
--- a/Tethering/AndroidManifest_InProcess.xml
+++ /dev/null
@@ -1,34 +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="com.android.networkstack.tethering.inprocess"
-          android:sharedUserId="android.uid.system"
-          android:process="system">
-    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
-    <application>
-        <service android:name="com.android.networkstack.tethering.TetheringService"
-                 android:process="system"
-                 android:permission="android.permission.MAINLINE_NETWORK_STACK"
-                 android:exported="true">
-            <intent-filter>
-                <action android:name="android.net.ITetheringConnector.InProcess"/>
-            </intent-filter>
-        </service>
-    </application>
-</manifest>
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index e59c8e4..cf9b359 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -18,26 +18,19 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-prebuilt_etc {
-    name: "TetheringInProcessFlag",
-    src: "in-process",
-    filename_from_src: true,
-    sub_dir: "flag",
-}
-
-prebuilt_etc {
-    name: "TetheringOutOfProcessFlag",
-    src: "out-of-process",
-    filename_from_src: true,
-    sub_dir: "flag",
-}
-
 // Defaults to enable/disable java targets which uses development APIs. "enabled" may have a
 // different value depending on the branch.
 java_defaults {
     name: "ConnectivityNextEnableDefaults",
     enabled: true,
 }
+java_defaults {
+    name: "NetworkStackApiShimSettingsForCurrentBranch",
+    // API shims to include in the networking modules built from the branch. Branches that disable
+    // the "next" targets must use stable shims (latest stable API level) instead of current shims
+    // (X_current API level).
+    static_libs: ["NetworkStackApiCurrentShims"],
+}
 apex_defaults {
     name: "ConnectivityApexDefaults",
     // Tethering app to include in the AOSP apex. Branches that disable the "next" targets may use
@@ -50,11 +43,30 @@
 // as the above target may have different "enabled" values
 // depending on the branch
 
+apex_defaults {
+    name: "CronetInTetheringApexDefaults",
+    jni_libs: [
+        "cronet_aml_components_cronet_android_cronet",
+        "//external/cronet/third_party/boringssl:libcrypto",
+        "//external/cronet/third_party/boringssl:libssl",
+    ],
+    arch: {
+        riscv64: {
+            // TODO: remove this when there is a riscv64 libcronet
+            exclude_jni_libs: [
+                "cronet_aml_components_cronet_android_cronet",
+                "//external/cronet/third_party/boringssl:libcrypto",
+                "//external/cronet/third_party/boringssl:libssl",
+            ],
+        },
+    },
+}
+
 apex {
     name: "com.android.tethering",
     defaults: [
         "ConnectivityApexDefaults",
-        "CronetApexDefaults",
+        "CronetInTetheringApexDefaults",
         "r-launched-apex-module",
     ],
     compile_multilib: "both",
@@ -100,11 +112,7 @@
         "ServiceConnectivityResources",
         "HalfSheetUX",
     ],
-    prebuilts: [
-        "current_sdkinfo",
-        "privapp_allowlist_com.android.tethering",
-        "TetheringOutOfProcessFlag",
-    ],
+    prebuilts: ["current_sdkinfo"],
     manifest: "manifest.json",
     key: "com.android.tethering.key",
     // Indicates that pre-installed version of this apex can be compressed.
@@ -115,7 +123,6 @@
 
     compat_configs: [
         "connectivity-platform-compat-config",
-        "connectivity-t-platform-compat-config",
     ],
 }
 
@@ -183,6 +190,7 @@
             "android.app.usage",
             "android.nearby",
             "android.net",
+            "android.net.http",
             "android.net.netstats",
             "android.net.util",
         ],
@@ -197,8 +205,10 @@
             "android.nearby.aidl",
             "android.net.apf",
             "android.net.connectivity",
+            "android.net.http.apihelpers",
             "android.net.netstats.provider",
             "android.net.nsd",
+            "android.net.wear",
         ],
     },
 }
@@ -208,27 +218,3 @@
     standalone_contents: ["service-connectivity"],
     apex_available: ["com.android.tethering"],
 }
-
-override_apex {
-    name: "com.android.tethering.inprocess",
-    base: "com.android.tethering",
-    package_name: "com.android.tethering.inprocess",
-    enabled: enable_tethering_next_apex,
-    bpfs: [
-        "block.o",
-        "clatd.o",
-        "dscpPolicy.o",
-        "netd.o",
-        "offload@inprocess.o",
-        "test@inprocess.o",
-    ],
-    apps: [
-        "ServiceConnectivityResources",
-        "InProcessTethering",
-    ],
-    prebuilts: [
-        "current_sdkinfo",
-        "privapp_allowlist_com.android.tethering",
-        "TetheringInProcessFlag",
-    ],
-}
diff --git a/Tethering/apex/in-process b/Tethering/apex/in-process
deleted file mode 100644
index e69de29..0000000
--- a/Tethering/apex/in-process
+++ /dev/null
diff --git a/Tethering/apex/out-of-process b/Tethering/apex/out-of-process
deleted file mode 100644
index e69de29..0000000
--- a/Tethering/apex/out-of-process
+++ /dev/null
diff --git a/Tethering/apex/permissions/Android.bp b/Tethering/apex/permissions/Android.bp
index ac9ec65..69c1aa2 100644
--- a/Tethering/apex/permissions/Android.bp
+++ b/Tethering/apex/permissions/Android.bp
@@ -19,10 +19,7 @@
     default_visibility: ["//packages/modules/Connectivity/Tethering:__subpackages__"],
 }
 
-prebuilt_etc {
+filegroup {
     name: "privapp_allowlist_com.android.tethering",
-    sub_dir: "permissions",
-    filename: "permissions.xml",
-    src: "permissions.xml",
-    installable: false,
+    srcs: ["permissions.xml"],
 }
\ No newline at end of file
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index 9ca3f14..a4db776 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -19,9 +19,15 @@
 
 java_sdk_library {
     name: "framework-tethering",
-    defaults: ["framework-module-defaults"],
+    defaults: [
+        "framework-tethering-defaults",
+    ],
     impl_library_visibility: [
         "//packages/modules/Connectivity/Tethering:__subpackages__",
+        "//packages/modules/Connectivity/framework",
+        "//packages/modules/Connectivity/framework-t",
+        "//packages/modules/Connectivity/service",
+        "//packages/modules/Connectivity/service-t",
 
         // Using for test only
         "//cts/tests/netlegacy22.api",
@@ -41,42 +47,64 @@
         "//packages/modules/NetworkStack/tests:__subpackages__",
         "//packages/modules/Wifi/service/tests/wifitests",
     ],
-
-    srcs: [":framework-tethering-srcs"],
-    libs: ["framework-connectivity.stubs.module_lib"],
     stub_only_libs: ["framework-connectivity.stubs.module_lib"],
+
+    jarjar_rules: ":framework-tethering-jarjar-rules",
+    installable: true,
+
+    hostdex: true, // for hiddenapi check
+    permitted_packages: ["android.net"],
+    lint: { strict_updatability_linting: true },
+}
+
+java_library {
+  name: "framework-tethering-pre-jarjar",
+  defaults: [
+    "framework-tethering-defaults",
+  ],
+}
+
+java_genrule {
+    name: "framework-tethering-jarjar-rules",
+    tool_files: [
+        ":framework-tethering-pre-jarjar{.jar}",
+        ":framework-tethering.stubs.module_lib{.jar}",
+        "jarjar-excludes.txt",
+    ],
+    tools: [
+        "jarjar-rules-generator",
+    ],
+    out: ["framework_tethering_jarjar_rules.txt"],
+    cmd: "$(location jarjar-rules-generator) " +
+        "$(location :framework-tethering-pre-jarjar{.jar}) " +
+        "--apistubs $(location :framework-tethering.stubs.module_lib{.jar}) " +
+        "--prefix android.net.http.internal " +
+        "--excludes $(location jarjar-excludes.txt) " +
+        "--output $(out)",
+}
+
+java_defaults {
+    name: "framework-tethering-defaults",
+    defaults: ["framework-module-defaults"],
+    srcs: [
+      ":framework-tethering-srcs"
+    ],
+    libs: ["framework-connectivity.stubs.module_lib"],
     aidl: {
         include_dirs: [
             "packages/modules/Connectivity/framework/aidl-export",
         ],
     },
-
-    jarjar_rules: "jarjar-rules.txt",
-    installable: true,
-
-    hostdex: true, // for hiddenapi check
     apex_available: ["com.android.tethering"],
-    permitted_packages: ["android.net"],
     min_sdk_version: "30",
-    lint: { strict_updatability_linting: true },
 }
 
 filegroup {
     name: "framework-tethering-srcs",
+    defaults: ["framework-sources-module-defaults"],
     srcs: [
-        "src/android/net/TetheredClient.aidl",
-        "src/android/net/TetheredClient.java",
-        "src/android/net/TetheringManager.java",
-        "src/android/net/TetheringConstants.java",
-        "src/android/net/IIntResultListener.aidl",
-        "src/android/net/ITetheringEventCallback.aidl",
-        "src/android/net/ITetheringConnector.aidl",
-        "src/android/net/TetheringCallbackStartedParcel.aidl",
-        "src/android/net/TetheringConfigurationParcel.aidl",
-        "src/android/net/TetheringRequestParcel.aidl",
-        "src/android/net/TetherStatesParcel.aidl",
-        "src/android/net/TetheringInterface.aidl",
-        "src/android/net/TetheringInterface.java",
+        "src/**/*.aidl",
+        "src/**/*.java",
     ],
     path: "src"
 }
diff --git a/Tethering/common/TetheringLib/jarjar-excludes.txt b/Tethering/common/TetheringLib/jarjar-excludes.txt
new file mode 100644
index 0000000..50bc186
--- /dev/null
+++ b/Tethering/common/TetheringLib/jarjar-excludes.txt
@@ -0,0 +1,2 @@
+# Don't touch anything that's already under android.net
+android\.net\..+
\ No newline at end of file
diff --git a/Tethering/common/TetheringLib/jarjar-rules.txt b/Tethering/common/TetheringLib/jarjar-rules.txt
deleted file mode 100644
index e459fad..0000000
--- a/Tethering/common/TetheringLib/jarjar-rules.txt
+++ /dev/null
@@ -1 +0,0 @@
-# jarjar rules for the bootclasspath tethering framework library here
\ No newline at end of file
diff --git a/Cronet/AndroidManifest.xml b/Tethering/common/TetheringLib/src/android/net/wear/ICompanionDeviceManagerProxy.aidl
similarity index 66%
rename from Cronet/AndroidManifest.xml
rename to Tethering/common/TetheringLib/src/android/net/wear/ICompanionDeviceManagerProxy.aidl
index f3b3c3e..f8c39bf 100644
--- a/Cronet/AndroidManifest.xml
+++ b/Tethering/common/TetheringLib/src/android/net/wear/ICompanionDeviceManagerProxy.aidl
@@ -1,7 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -15,9 +13,12 @@
  * 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.cronet"
-  android:versionCode="11"
-  android:versionName="R-initial">
-</manifest>
+
+package android.net.wear;
+
+import android.companion.AssociationInfo;
+
+/** @hide */
+interface ICompanionDeviceManagerProxy {
+    List<AssociationInfo> getAllAssociations();
+}
diff --git a/Tethering/jni/com_android_networkstack_tethering_util_TetheringUtils.cpp b/Tethering/jni/com_android_networkstack_tethering_util_TetheringUtils.cpp
index 6699c0d..14e4b9a 100644
--- a/Tethering/jni/com_android_networkstack_tethering_util_TetheringUtils.cpp
+++ b/Tethering/jni/com_android_networkstack_tethering_util_TetheringUtils.cpp
@@ -18,21 +18,19 @@
 #include <error.h>
 #include <jni.h>
 #include <linux/filter.h>
+#include <linux/ipv6.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include <netjniutils/netjniutils.h>
 #include <net/if.h>
 #include <netinet/ether.h>
-#include <netinet/ip6.h>
 #include <netinet/icmp6.h>
 #include <sys/socket.h>
 #include <stdio.h>
 
-namespace android {
+#include <bpf/BpfClassic.h>
 
-static const uint32_t kIPv6NextHeaderOffset = offsetof(ip6_hdr, ip6_nxt);
-static const uint32_t kIPv6PayloadStart = sizeof(ip6_hdr);
-static const uint32_t kICMPv6TypeOffset = kIPv6PayloadStart + offsetof(icmp6_hdr, icmp6_type);
+namespace android {
 
 static void throwSocketException(JNIEnv *env, const char* msg, int error) {
     jniThrowExceptionFmt(env, "java/net/SocketException", "%s: %s", msg, strerror(error));
@@ -42,18 +40,14 @@
         uint32_t type) {
     sock_filter filter_code[] = {
         // Check header is ICMPv6.
-        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kIPv6NextHeaderOffset),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    IPPROTO_ICMPV6, 0, 3),
+        BPF_LOAD_IPV6_U8(nexthdr),
+        BPF2_REJECT_IF_NOT_EQUAL(IPPROTO_ICMPV6),
 
         // Check ICMPv6 type.
-        BPF_STMT(BPF_LD  | BPF_B   | BPF_ABS,  kICMPv6TypeOffset),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    type, 0, 1),
+        BPF_LOAD_NET_RELATIVE_U8(sizeof(ipv6hdr) + offsetof(icmp6_hdr, icmp6_type)),
+        BPF2_REJECT_IF_NOT_EQUAL(type),
 
-        // Accept.
-        BPF_STMT(BPF_RET | BPF_K,              0xffff),
-
-        // Reject.
-        BPF_STMT(BPF_RET | BPF_K,              0)
+        BPF_ACCEPT,
     };
 
     const sock_fprog filter = {
@@ -67,17 +61,17 @@
     }
 }
 
-static void com_android_networkstack_tethering_util_setupNaSocket(JNIEnv *env, jobject clazz,
+static void com_android_networkstack_tethering_util_setupNaSocket(JNIEnv *env, jclass clazz,
         jobject javaFd) {
     com_android_networkstack_tethering_util_setupIcmpFilter(env, javaFd, ND_NEIGHBOR_ADVERT);
 }
 
-static void com_android_networkstack_tethering_util_setupNsSocket(JNIEnv *env, jobject clazz,
+static void com_android_networkstack_tethering_util_setupNsSocket(JNIEnv *env, jclass clazz,
         jobject javaFd) {
     com_android_networkstack_tethering_util_setupIcmpFilter(env, javaFd, ND_NEIGHBOR_SOLICIT);
 }
 
-static void com_android_networkstack_tethering_util_setupRaSocket(JNIEnv *env, jobject clazz,
+static void com_android_networkstack_tethering_util_setupRaSocket(JNIEnv *env, jclass clazz,
         jobject javaFd, jint ifIndex) {
     static const int kLinkLocalHopLimit = 255;
 
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 8cf13d3..6affb62 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -17,6 +17,15 @@
 package android.net.ip;
 
 import static android.net.RouteInfo.RTN_UNICAST;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
+import static android.net.TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
 import static android.net.TetheringManager.TetheringRequest.checkStaticAddressConfiguration;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
@@ -310,7 +319,7 @@
         mDeps = deps;
         mTetheringMetrics = tetheringMetrics;
         resetLinkProperties();
-        mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
+        mLastError = TETHER_ERROR_NO_ERROR;
         mServingMode = STATE_AVAILABLE;
 
         mIpNeighborMonitor = mDeps.getIpNeighborMonitor(getHandler(), mLog,
@@ -398,8 +407,8 @@
 
     /** Internals. */
 
-    private boolean startIPv4() {
-        return configureIPv4(true);
+    private boolean startIPv4(int scope) {
+        return configureIPv4(true, scope);
     }
 
     /**
@@ -475,7 +484,7 @@
         }
 
         private void handleError() {
-            mLastError = TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
+            mLastError = TETHER_ERROR_DHCPSERVER_ERROR;
             transitionTo(mInitialState);
         }
     }
@@ -583,7 +592,7 @@
                     public void callback(int statusCode) {
                         if (statusCode != STATUS_SUCCESS) {
                             mLog.e("Error stopping DHCP server: " + statusCode);
-                            mLastError = TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
+                            mLastError = TETHER_ERROR_DHCPSERVER_ERROR;
                             // Not much more we can do here
                         }
                         mDhcpLeases.clear();
@@ -609,7 +618,7 @@
     }
 
     private void stopIPv4() {
-        configureIPv4(false);
+        configureIPv4(false /* enabled */, CONNECTIVITY_SCOPE_GLOBAL /* not used */);
         // NOTE: All of configureIPv4() will be refactored out of existence
         // into calls to InterfaceController, shared with startIPv4().
         mInterfaceCtrl.clearIPv4Address();
@@ -620,11 +629,11 @@
         mStaticIpv4ClientAddr = null;
     }
 
-    private boolean configureIPv4(boolean enabled) {
+    private boolean configureIPv4(boolean enabled, int scope) {
         if (VDBG) Log.d(TAG, "configureIPv4(" + enabled + ")");
 
         if (enabled) {
-            mIpv4Address = requestIpv4Address(true /* useLastAddress */);
+            mIpv4Address = requestIpv4Address(scope, true /* useLastAddress */);
         }
 
         if (mIpv4Address == null) {
@@ -672,12 +681,12 @@
         return (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) && !SdkLevel.isAtLeastT();
     }
 
-    private LinkAddress requestIpv4Address(final boolean useLastAddress) {
+    private LinkAddress requestIpv4Address(final int scope, final boolean useLastAddress) {
         if (mStaticIpv4ServerAddr != null) return mStaticIpv4ServerAddr;
 
         if (shouldNotConfigureBluetoothInterface()) return new LinkAddress(BLUETOOTH_IFACE_ADDR);
 
-        return mPrivateAddressCoordinator.requestDownstreamAddress(this, useLastAddress);
+        return mPrivateAddressCoordinator.requestDownstreamAddress(this, scope, useLastAddress);
     }
 
     private boolean startIPv6() {
@@ -991,67 +1000,6 @@
         }
     }
 
-    private void handleNewPrefixRequest(@NonNull final IpPrefix currentPrefix) {
-        if (!currentPrefix.contains(mIpv4Address.getAddress())
-                || currentPrefix.getPrefixLength() != mIpv4Address.getPrefixLength()) {
-            Log.e(TAG, "Invalid prefix: " + currentPrefix);
-            return;
-        }
-
-        final LinkAddress deprecatedLinkAddress = mIpv4Address;
-        mIpv4Address = requestIpv4Address(false);
-        if (mIpv4Address == null) {
-            mLog.e("Fail to request a new downstream prefix");
-            return;
-        }
-        final Inet4Address srvAddr = (Inet4Address) mIpv4Address.getAddress();
-
-        // Add new IPv4 address on the interface.
-        if (!mInterfaceCtrl.addAddress(srvAddr, currentPrefix.getPrefixLength())) {
-            mLog.e("Failed to add new IP " + srvAddr);
-            return;
-        }
-
-        // Remove deprecated routes from local network.
-        removeRoutesFromLocalNetwork(
-                Collections.singletonList(getDirectConnectedRoute(deprecatedLinkAddress)));
-        mLinkProperties.removeLinkAddress(deprecatedLinkAddress);
-
-        // Add new routes to local network.
-        addRoutesToLocalNetwork(
-                Collections.singletonList(getDirectConnectedRoute(mIpv4Address)));
-        mLinkProperties.addLinkAddress(mIpv4Address);
-
-        // Update local DNS caching server with new IPv4 address, otherwise, dnsmasq doesn't
-        // listen on the interface configured with new IPv4 address, that results DNS validation
-        // failure of downstream client even if appropriate routes have been configured.
-        try {
-            mNetd.tetherApplyDnsInterfaces();
-        } catch (ServiceSpecificException | RemoteException e) {
-            mLog.e("Failed to update local DNS caching server");
-            return;
-        }
-        sendLinkProperties();
-
-        // Notify DHCP server that new prefix/route has been applied on IpServer.
-        final Inet4Address clientAddr = mStaticIpv4ClientAddr == null ? null :
-                (Inet4Address) mStaticIpv4ClientAddr.getAddress();
-        final DhcpServingParamsParcel params = makeServingParams(srvAddr /* defaultRouter */,
-                srvAddr /* dnsServer */, mIpv4Address /* serverLinkAddress */, clientAddr);
-        try {
-            mDhcpServer.updateParams(params, new OnHandlerStatusCallback() {
-                    @Override
-                    public void callback(int statusCode) {
-                        if (statusCode != STATUS_SUCCESS) {
-                            mLog.e("Error updating DHCP serving params: " + statusCode);
-                        }
-                    }
-            });
-        } catch (RemoteException e) {
-            mLog.e("Error updating DHCP serving params", e);
-        }
-    }
-
     private byte getHopLimit(String upstreamIface, int adjustTTL) {
         try {
             int upstreamHopLimit = Integer.parseUnsignedInt(
@@ -1134,7 +1082,7 @@
             maybeLogMessage(this, message.what);
             switch (message.what) {
                 case CMD_TETHER_REQUESTED:
-                    mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
+                    mLastError = TETHER_ERROR_NO_ERROR;
                     switch (message.arg1) {
                         case STATE_LOCAL_ONLY:
                             maybeConfigureStaticIp((TetheringRequestParcel) message.obj);
@@ -1166,13 +1114,38 @@
         mBpfCoordinator.stopMonitoring(this);
     }
 
-    class BaseServingState extends State {
+    abstract class BaseServingState extends State {
+        private final int mDesiredInterfaceState;
+
+        BaseServingState(int interfaceState) {
+            mDesiredInterfaceState = interfaceState;
+        }
+
         @Override
         public void enter() {
             startConntrackMonitoring();
 
-            if (!startIPv4()) {
-                mLastError = TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
+            startServingInterface();
+
+            if (mLastError != TETHER_ERROR_NO_ERROR) {
+                transitionTo(mInitialState);
+            }
+
+            if (DBG) Log.d(TAG, getStateString(mDesiredInterfaceState) + " serve " + mIfaceName);
+            sendInterfaceState(mDesiredInterfaceState);
+        }
+
+        private int getScope() {
+            if (mDesiredInterfaceState == STATE_TETHERED) {
+                return CONNECTIVITY_SCOPE_GLOBAL;
+            }
+
+            return CONNECTIVITY_SCOPE_LOCAL;
+        }
+
+        private void startServingInterface() {
+            if (!startIPv4(getScope())) {
+                mLastError = TETHER_ERROR_IFACE_CFG_ERROR;
                 return;
             }
 
@@ -1180,7 +1153,7 @@
                 NetdUtils.tetherInterface(mNetd, mIfaceName, asIpPrefix(mIpv4Address));
             } catch (RemoteException | ServiceSpecificException | IllegalStateException e) {
                 mLog.e("Error Tethering", e);
-                mLastError = TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+                mLastError = TETHER_ERROR_TETHER_IFACE_ERROR;
                 return;
             }
 
@@ -1201,7 +1174,7 @@
             try {
                 NetdUtils.untetherInterface(mNetd, mIfaceName);
             } catch (RemoteException | ServiceSpecificException e) {
-                mLastError = TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+                mLastError = TETHER_ERROR_UNTETHER_IFACE_ERROR;
                 mLog.e("Failed to untether interface: " + e);
             }
 
@@ -1234,7 +1207,7 @@
                 case CMD_START_TETHERING_ERROR:
                 case CMD_STOP_TETHERING_ERROR:
                 case CMD_SET_DNS_FORWARDERS_ERROR:
-                    mLastError = TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
+                    mLastError = TETHER_ERROR_INTERNAL_ERROR;
                     transitionTo(mInitialState);
                     break;
                 case CMD_NEW_PREFIX_REQUEST:
@@ -1250,6 +1223,67 @@
             }
             return true;
         }
+
+        private void handleNewPrefixRequest(@NonNull final IpPrefix currentPrefix) {
+            if (!currentPrefix.contains(mIpv4Address.getAddress())
+                    || currentPrefix.getPrefixLength() != mIpv4Address.getPrefixLength()) {
+                Log.e(TAG, "Invalid prefix: " + currentPrefix);
+                return;
+            }
+
+            final LinkAddress deprecatedLinkAddress = mIpv4Address;
+            mIpv4Address = requestIpv4Address(getScope(), false);
+            if (mIpv4Address == null) {
+                mLog.e("Fail to request a new downstream prefix");
+                return;
+            }
+            final Inet4Address srvAddr = (Inet4Address) mIpv4Address.getAddress();
+
+            // Add new IPv4 address on the interface.
+            if (!mInterfaceCtrl.addAddress(srvAddr, currentPrefix.getPrefixLength())) {
+                mLog.e("Failed to add new IP " + srvAddr);
+                return;
+            }
+
+            // Remove deprecated routes from local network.
+            removeRoutesFromLocalNetwork(
+                    Collections.singletonList(getDirectConnectedRoute(deprecatedLinkAddress)));
+            mLinkProperties.removeLinkAddress(deprecatedLinkAddress);
+
+            // Add new routes to local network.
+            addRoutesToLocalNetwork(
+                    Collections.singletonList(getDirectConnectedRoute(mIpv4Address)));
+            mLinkProperties.addLinkAddress(mIpv4Address);
+
+            // Update local DNS caching server with new IPv4 address, otherwise, dnsmasq doesn't
+            // listen on the interface configured with new IPv4 address, that results DNS validation
+            // failure of downstream client even if appropriate routes have been configured.
+            try {
+                mNetd.tetherApplyDnsInterfaces();
+            } catch (ServiceSpecificException | RemoteException e) {
+                mLog.e("Failed to update local DNS caching server");
+                return;
+            }
+            sendLinkProperties();
+
+            // Notify DHCP server that new prefix/route has been applied on IpServer.
+            final Inet4Address clientAddr = mStaticIpv4ClientAddr == null ? null :
+                    (Inet4Address) mStaticIpv4ClientAddr.getAddress();
+            final DhcpServingParamsParcel params = makeServingParams(srvAddr /* defaultRouter */,
+                    srvAddr /* dnsServer */, mIpv4Address /* serverLinkAddress */, clientAddr);
+            try {
+                mDhcpServer.updateParams(params, new OnHandlerStatusCallback() {
+                        @Override
+                        public void callback(int statusCode) {
+                            if (statusCode != STATUS_SUCCESS) {
+                                mLog.e("Error updating DHCP serving params: " + statusCode);
+                            }
+                        }
+                });
+            } catch (RemoteException e) {
+                mLog.e("Error updating DHCP serving params", e);
+            }
+        }
     }
 
     // Handling errors in BaseServingState.enter() by transitioning is
@@ -1258,15 +1292,8 @@
     // and forwarding and NAT rules should be handled by a coordinating
     // functional element outside of IpServer.
     class LocalHotspotState extends BaseServingState {
-        @Override
-        public void enter() {
-            super.enter();
-            if (mLastError != TetheringManager.TETHER_ERROR_NO_ERROR) {
-                transitionTo(mInitialState);
-            }
-
-            if (DBG) Log.d(TAG, "Local hotspot " + mIfaceName);
-            sendInterfaceState(STATE_LOCAL_ONLY);
+        LocalHotspotState() {
+            super(STATE_LOCAL_ONLY);
         }
 
         @Override
@@ -1294,15 +1321,8 @@
     // and forwarding and NAT rules should be handled by a coordinating
     // functional element outside of IpServer.
     class TetheredState extends BaseServingState {
-        @Override
-        public void enter() {
-            super.enter();
-            if (mLastError != TetheringManager.TETHER_ERROR_NO_ERROR) {
-                transitionTo(mInitialState);
-            }
-
-            if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
-            sendInterfaceState(STATE_TETHERED);
+        TetheredState() {
+            super(STATE_TETHERED);
         }
 
         @Override
@@ -1405,7 +1425,7 @@
                         } catch (RemoteException | ServiceSpecificException e) {
                             mLog.e("Exception enabling NAT: " + e.toString());
                             cleanupUpstream();
-                            mLastError = TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR;
+                            mLastError = TETHER_ERROR_ENABLE_FORWARDING_ERROR;
                             transitionTo(mInitialState);
                             return true;
                         }
@@ -1454,7 +1474,7 @@
         @Override
         public void enter() {
             mIpNeighborMonitor.stop();
-            mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
+            mLastError = TETHER_ERROR_NO_ERROR;
             sendInterfaceState(STATE_UNAVAILABLE);
         }
     }
diff --git a/Tethering/src/android/net/ip/NeighborPacketForwarder.java b/Tethering/src/android/net/ip/NeighborPacketForwarder.java
index 8384562..04cb710 100644
--- a/Tethering/src/android/net/ip/NeighborPacketForwarder.java
+++ b/Tethering/src/android/net/ip/NeighborPacketForwarder.java
@@ -18,12 +18,14 @@
 
 import static android.system.OsConstants.AF_INET6;
 import static android.system.OsConstants.AF_PACKET;
+import static android.system.OsConstants.ENODEV;
 import static android.system.OsConstants.ETH_P_IPV6;
 import static android.system.OsConstants.IPPROTO_RAW;
 import static android.system.OsConstants.SOCK_DGRAM;
 import static android.system.OsConstants.SOCK_NONBLOCK;
 import static android.system.OsConstants.SOCK_RAW;
-import static android.system.OsConstants.ENODEV;
+
+import static com.android.net.module.util.SocketUtils.closeSocketQuietly;
 
 import android.net.util.SocketUtils;
 import android.os.Handler;
@@ -36,7 +38,6 @@
 import com.android.networkstack.tethering.util.TetheringUtils;
 
 import java.io.FileDescriptor;
-import java.io.IOException;
 import java.net.Inet6Address;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
@@ -105,15 +106,6 @@
         }
     }
 
-    // TODO: move NetworkStackUtils.closeSocketQuietly to
-    // frameworks/libs/net/common/device/com/android/net/module/util/[someclass].
-    private void closeSocketQuietly(FileDescriptor fd) {
-        try {
-            SocketUtils.closeSocket(fd);
-        } catch (IOException ignored) {
-        }
-    }
-
     @Override
     protected FileDescriptor createFd() {
         try {
diff --git a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
index c452e55..18c2171 100644
--- a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
+++ b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
@@ -88,13 +88,13 @@
     private static final int MIN_RTR_ADV_INTERVAL_SEC = 300;
     private static final int MAX_RTR_ADV_INTERVAL_SEC = 600;
     // In general, router, prefix, and DNS lifetimes are all advised to be
-    // greater than or equal to 3 * MAX_RTR_ADV_INTERVAL.  Here, we double
+    // greater than or equal to 3 * MAX_RTR_ADV_INTERVAL.  Here, we quadruple
     // that to allow for multicast packet loss.
     //
     // This MAX_RTR_ADV_INTERVAL_SEC and DEFAULT_LIFETIME are also consistent
     // with the https://tools.ietf.org/html/rfc7772#section-4 discussion of
     // "approximately 7 RAs per hour".
-    private static final int DEFAULT_LIFETIME = 6 * MAX_RTR_ADV_INTERVAL_SEC;
+    private static final int DEFAULT_LIFETIME = 12 * MAX_RTR_ADV_INTERVAL_SEC;
     // From https://tools.ietf.org/html/rfc4861#section-10 .
     private static final int MIN_DELAY_BETWEEN_RAS_SEC = 3;
     // Both initial and final RAs, but also for changes in RA contents.
@@ -129,9 +129,6 @@
         // Tethered traffic will have the hop limit properly decremented.
         // Consequently, set the hoplimit greater by one than the upstream
         // unicast hop limit.
-        //
-        // TODO: Dynamically pass down the IPV6_UNICAST_HOPS value from the
-        // upstream interface for more correct behaviour.
         static final byte DEFAULT_HOPLIMIT = 65;
 
         public boolean hasDefaultRoute;
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 51c7c9c..976f5df 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -27,6 +27,7 @@
 import static android.system.OsConstants.ETH_P_IP;
 import static android.system.OsConstants.ETH_P_IPV6;
 
+import static com.android.net.module.util.NetworkStackConstants.IPV4_MIN_MTU;
 import static com.android.net.module.util.ip.ConntrackMonitor.ConntrackEvent;
 import static com.android.networkstack.tethering.BpfUtils.DOWNSTREAM;
 import static com.android.networkstack.tethering.BpfUtils.UPSTREAM;
@@ -36,6 +37,7 @@
 
 import android.app.usage.NetworkStatsManager;
 import android.net.INetd;
+import android.net.LinkProperties;
 import android.net.MacAddress;
 import android.net.NetworkStats;
 import android.net.NetworkStats.Entry;
@@ -74,7 +76,7 @@
 import com.android.net.module.util.ip.ConntrackMonitor.ConntrackEventConsumer;
 import com.android.net.module.util.netlink.ConntrackMessage;
 import com.android.net.module.util.netlink.NetlinkConstants;
-import com.android.net.module.util.netlink.NetlinkSocket;
+import com.android.net.module.util.netlink.NetlinkUtils;
 import com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim;
 import com.android.networkstack.tethering.util.TetheringUtils.ForwardedStats;
 
@@ -82,6 +84,8 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -143,6 +147,8 @@
     static final int NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED = 432_000;
     @VisibleForTesting
     static final int NF_CONNTRACK_UDP_TIMEOUT_STREAM = 180;
+    @VisibleForTesting
+    static final int INVALID_MTU = 0;
 
     // List of TCP port numbers which aren't offloaded because the packets require the netfilter
     // conntrack helper. See also TetherController::setForwardRules in netd.
@@ -263,6 +269,10 @@
     // TODO: Support multi-upstream interfaces.
     private int mLastIPv4UpstreamIfindex = 0;
 
+    // Tracks the IPv4 upstream interface information.
+    @Nullable
+    private UpstreamInfo mIpv4UpstreamInfo = null;
+
     // Runnable that used by scheduling next polling of stats.
     private final Runnable mScheduledPollingStats = () -> {
         updateForwardedStats();
@@ -320,6 +330,19 @@
             return SdkLevel.isAtLeastS();
         }
 
+        /**
+         * Gets the MTU of the given interface.
+         */
+        public int getNetworkInterfaceMtu(@NonNull String iface) {
+            try {
+                final NetworkInterface networkInterface = NetworkInterface.getByName(iface);
+                return networkInterface == null ? INVALID_MTU : networkInterface.getMTU();
+            } catch (SocketException e) {
+                Log.e(TAG, "Could not get MTU for interface " + iface, e);
+                return INVALID_MTU;
+            }
+        }
+
         /** Get downstream4 BPF map. */
         @Nullable public IBpfMap<Tether4Key, Tether4Value> getBpfDownstream4Map() {
             if (!isAtLeastS()) return null;
@@ -856,6 +879,27 @@
         return true;
     }
 
+    private int getMtu(@NonNull final String ifaceName, @NonNull final LinkProperties lp) {
+        int mtu = INVALID_MTU;
+
+        if (ifaceName.equals(lp.getInterfaceName())) {
+            mtu = lp.getMtu();
+        }
+
+        // Get mtu via kernel if mtu is not found in LinkProperties.
+        if (mtu == INVALID_MTU) {
+            mtu = mDeps.getNetworkInterfaceMtu(ifaceName);
+        }
+
+        // Use default mtu if can't find any.
+        if (mtu == INVALID_MTU) mtu = NetworkStackConstants.ETHER_MTU;
+
+        // Clamp to minimum ipv4 mtu
+        if (mtu < IPV4_MIN_MTU) mtu = IPV4_MIN_MTU;
+
+        return mtu;
+    }
+
     /**
      * Call when UpstreamNetworkState may be changed.
      * If upstream has ipv4 for tethering, update this new UpstreamNetworkState
@@ -868,6 +912,7 @@
         if (!isUsingBpf()) return;
 
         int upstreamIndex = 0;
+        int mtu = INVALID_MTU;
 
         // This will not work on a network that is using 464xlat because hasIpv4Address will not be
         // true.
@@ -877,6 +922,8 @@
             final String ifaceName = ns.linkProperties.getInterfaceName();
             final InterfaceParams params = mDeps.getInterfaceParams(ifaceName);
             final boolean isVcn = isVcnInterface(ifaceName);
+            mtu = getMtu(ifaceName, ns.linkProperties);
+
             if (!isVcn && params != null && !params.hasMacAddress /* raw ip upstream only */) {
                 upstreamIndex = params.index;
             }
@@ -905,8 +952,11 @@
         // after the upstream is lost do not incorrectly add rules pointing at the upstream.
         if (upstreamIndex == 0) {
             mIpv4UpstreamIndices.clear();
+            mIpv4UpstreamInfo = null;
             return;
         }
+
+        mIpv4UpstreamInfo = new UpstreamInfo(upstreamIndex, mtu);
         Collection<InetAddress> addresses = ns.linkProperties.getAddresses();
         for (final InetAddress addr: addresses) {
             if (isValidUpstreamIpv4Address(addr)) {
@@ -1051,6 +1101,9 @@
         }
         pw.decreaseIndent();
 
+        pw.println("IPv4 Upstream Information: "
+                + (mIpv4UpstreamInfo != null ? mIpv4UpstreamInfo : "<empty>"));
+
         pw.println();
         pw.println("Forwarding counters:");
         pw.increaseIndent();
@@ -1258,10 +1311,10 @@
 
         final String ageStr = (value.lastUsed == 0) ? "-"
                 : String.format("%dms", (now - value.lastUsed) / 1_000_000);
-        return String.format("%s [%s] %d(%s) %s:%d -> %d(%s) %s:%d -> %s:%d [%s] %s",
+        return String.format("%s [%s] %d(%s) %s:%d -> %d(%s) %s:%d -> %s:%d [%s] %d %s",
                 l4protoToString(key.l4proto), key.dstMac, key.iif, getIfName(key.iif),
                 src4, key.srcPort, value.oif, getIfName(value.oif),
-                public4, publicPort, dst4, value.dstPort, value.ethDstMac, ageStr);
+                public4, publicPort, dst4, value.dstPort, value.ethDstMac, value.pmtu, ageStr);
     }
 
     private void dumpIpv4ForwardingRuleMap(long now, boolean downstream,
@@ -1283,13 +1336,13 @@
         try (IBpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map();
                 IBpfMap<Tether4Key, Tether4Value> downstreamMap = mDeps.getBpfDownstream4Map()) {
             pw.println("IPv4 Upstream: proto [inDstMac] iif(iface) src -> nat -> "
-                    + "dst [outDstMac] age");
+                    + "dst [outDstMac] pmtu age");
             pw.increaseIndent();
             dumpIpv4ForwardingRuleMap(now, UPSTREAM, upstreamMap, pw);
             pw.decreaseIndent();
 
             pw.println("IPv4 Downstream: proto [inDstMac] iif(iface) src -> nat -> "
-                    + "dst [outDstMac] age");
+                    + "dst [outDstMac] pmtu age");
             pw.increaseIndent();
             dumpIpv4ForwardingRuleMap(now, DOWNSTREAM, downstreamMap, pw);
             pw.decreaseIndent();
@@ -1540,6 +1593,28 @@
         }
     }
 
+    /** Upstream information class. */
+    private static final class UpstreamInfo {
+        // TODO: add clat interface information
+        public final int ifIndex;
+        public final int mtu;
+
+        private UpstreamInfo(final int ifIndex, final int mtu) {
+            this.ifIndex = ifIndex;
+            this.mtu = mtu;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(ifIndex, mtu);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("ifIndex: %d, mtu: %d", ifIndex, mtu);
+        }
+    }
+
     /**
      * A BPF tethering stats provider to provide network statistics to the system.
      * Note that this class' data may only be accessed on the handler thread.
@@ -1711,20 +1786,20 @@
 
         @NonNull
         private Tether4Value makeTetherUpstream4Value(@NonNull ConntrackEvent e,
-                int upstreamIndex) {
-            return new Tether4Value(upstreamIndex,
+                @NonNull UpstreamInfo upstreamInfo) {
+            return new Tether4Value(upstreamInfo.ifIndex,
                     NULL_MAC_ADDRESS /* ethDstMac (rawip) */,
                     NULL_MAC_ADDRESS /* ethSrcMac (rawip) */, ETH_P_IP,
-                    NetworkStackConstants.ETHER_MTU, toIpv4MappedAddressBytes(e.tupleReply.dstIp),
+                    upstreamInfo.mtu, toIpv4MappedAddressBytes(e.tupleReply.dstIp),
                     toIpv4MappedAddressBytes(e.tupleReply.srcIp), e.tupleReply.dstPort,
                     e.tupleReply.srcPort, 0 /* lastUsed, filled by bpf prog only */);
         }
 
         @NonNull
         private Tether4Value makeTetherDownstream4Value(@NonNull ConntrackEvent e,
-                @NonNull ClientInfo c, int upstreamIndex) {
+                @NonNull ClientInfo c, @NonNull UpstreamInfo upstreamInfo) {
             return new Tether4Value(c.downstreamIfindex,
-                    c.clientMac, c.downstreamMac, ETH_P_IP, NetworkStackConstants.ETHER_MTU,
+                    c.clientMac, c.downstreamMac, ETH_P_IP, upstreamInfo.mtu,
                     toIpv4MappedAddressBytes(e.tupleOrig.dstIp),
                     toIpv4MappedAddressBytes(e.tupleOrig.srcIp),
                     e.tupleOrig.dstPort, e.tupleOrig.srcPort,
@@ -1773,9 +1848,11 @@
                 return;
             }
 
-            final Tether4Value upstream4Value = makeTetherUpstream4Value(e, upstreamIndex);
+            if (mIpv4UpstreamInfo == null || mIpv4UpstreamInfo.ifIndex != upstreamIndex) return;
+
+            final Tether4Value upstream4Value = makeTetherUpstream4Value(e, mIpv4UpstreamInfo);
             final Tether4Value downstream4Value = makeTetherDownstream4Value(e, tetherClient,
-                    upstreamIndex);
+                    mIpv4UpstreamInfo);
 
             maybeAddDevMap(upstreamIndex, tetherClient.downstreamIfindex);
             maybeSetLimit(upstreamIndex);
@@ -2075,7 +2152,7 @@
         final byte[] msg = ConntrackMessage.newIPv4TimeoutUpdateRequest(
                 proto, src4, (int) srcPort, dst4, (int) dstPort, timeoutSec);
         try {
-            NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_NETFILTER, msg);
+            NetlinkUtils.sendOneShotKernelMessage(OsConstants.NETLINK_NETFILTER, msg);
         } catch (ErrnoException e) {
             // Lower the log level for the entry not existing. The conntrack entry may have been
             // deleted and not handled by the conntrack event monitor yet. In other words, the
@@ -2179,5 +2256,13 @@
         return mTetherClients;
     }
 
+    // Return map of upstream interface IPv4 address to interface index.
+    // This is used for testing only.
+    @NonNull
+    @VisibleForTesting
+    final HashMap<Inet4Address, Integer> getIpv4UpstreamIndicesForTesting() {
+        return mIpv4UpstreamIndices;
+    }
+
     private static native String[] getBpfCounterNames();
 }
diff --git a/Tethering/src/com/android/networkstack/tethering/ConnectedClientsTracker.java b/Tethering/src/com/android/networkstack/tethering/ConnectedClientsTracker.java
index 8a96988..5b19c4e 100644
--- a/Tethering/src/com/android/networkstack/tethering/ConnectedClientsTracker.java
+++ b/Tethering/src/com/android/networkstack/tethering/ConnectedClientsTracker.java
@@ -29,6 +29,8 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -49,6 +51,8 @@
     @NonNull
     private List<WifiClient> mLastWifiClients = Collections.emptyList();
     @NonNull
+    private List<WifiClient> mLastLocalOnlyClients = Collections.emptyList();
+    @NonNull
     private List<TetheredClient> mLastTetheredClients = Collections.emptyList();
 
     @VisibleForTesting
@@ -72,25 +76,44 @@
      *
      * <p>The new list can be obtained through {@link #getLastTetheredClients()}.
      * @param ipServers The IpServers used to assign addresses to clients.
-     * @param wifiClients The list of L2-connected WiFi clients. Null for no change since last
-     *                    update.
+     * @param wifiClients The list of L2-connected WiFi clients that are connected to a global
+     *                    hotspot. Null for no change since last update.
+     * @param localOnlyClients The list of L2-connected WiFi clients that are connected to localOnly
+     *                    hotspot. Null for no change since last update.
      * @return True if the list of clients changed since the last calculation.
      */
     public boolean updateConnectedClients(
-            Iterable<IpServer> ipServers, @Nullable List<WifiClient> wifiClients) {
+            Iterable<IpServer> ipServers, @Nullable List<WifiClient> wifiClients,
+            @Nullable List<WifiClient> localOnlyClients) {
         final long now = mClock.elapsedRealtime();
 
-        if (wifiClients != null) {
-            mLastWifiClients = wifiClients;
-        }
+        if (wifiClients != null) mLastWifiClients = wifiClients;
+        if (localOnlyClients != null) mLastLocalOnlyClients = localOnlyClients;
+
         final Set<MacAddress> wifiClientMacs = getClientMacs(mLastWifiClients);
+        final Set<MacAddress> localOnlyClientMacs = getClientMacs(mLastLocalOnlyClients);
 
         // Build the list of non-expired leases from all IpServers, grouped by mac address
         final Map<MacAddress, TetheredClient> clientsMap = new HashMap<>();
         for (IpServer server : ipServers) {
+            final Set<MacAddress> connectedClientMacs;
+            switch (server.servingMode()) {
+                case IpServer.STATE_TETHERED:
+                    connectedClientMacs = wifiClientMacs;
+                    break;
+                case IpServer.STATE_LOCAL_ONLY:
+                    // Before T, SAP and LOHS both use wifiClientMacs because
+                    // registerLocalOnlyHotspotSoftApCallback didn't exist.
+                    connectedClientMacs = SdkLevel.isAtLeastT()
+                            ? localOnlyClientMacs : wifiClientMacs;
+                    break;
+                default:
+                    continue;
+            }
+
             for (TetheredClient client : server.getAllLeases()) {
                 if (client.getTetheringType() == TETHERING_WIFI
-                        && !wifiClientMacs.contains(client.getMacAddress())) {
+                        && !connectedClientMacs.contains(client.getMacAddress())) {
                     // Skip leases of WiFi clients that are not (or no longer) L2-connected
                     continue;
                 }
@@ -104,11 +127,8 @@
         // TODO: add IPv6 addresses from netlink
 
         // Add connected WiFi clients that do not have any known address
-        for (MacAddress client : wifiClientMacs) {
-            if (clientsMap.containsKey(client)) continue;
-            clientsMap.put(client, new TetheredClient(
-                    client, Collections.emptyList() /* addresses */, TETHERING_WIFI));
-        }
+        addWifiClientsIfNoLeases(clientsMap, wifiClientMacs);
+        addWifiClientsIfNoLeases(clientsMap, localOnlyClientMacs);
 
         final HashSet<TetheredClient> clients = new HashSet<>(clientsMap.values());
         final boolean clientsChanged = clients.size() != mLastTetheredClients.size()
@@ -117,6 +137,15 @@
         return clientsChanged;
     }
 
+    private static void addWifiClientsIfNoLeases(
+            final Map<MacAddress, TetheredClient> clientsMap, final Set<MacAddress> clientMacs) {
+        for (MacAddress mac : clientMacs) {
+            if (clientsMap.containsKey(mac)) continue;
+            clientsMap.put(mac, new TetheredClient(
+                    mac, Collections.emptyList() /* addresses */, TETHERING_WIFI));
+        }
+    }
+
     private static void addLease(Map<MacAddress, TetheredClient> clientsMap, TetheredClient lease) {
         final TetheredClient aggregateClient = clientsMap.getOrDefault(
                 lease.getMacAddress(), lease);
diff --git a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
index 6d502ce..b88b13b 100644
--- a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
+++ b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
@@ -459,8 +459,9 @@
     }
 
     @VisibleForTesting
-    PendingIntent createRecheckAlarmIntent() {
+    PendingIntent createRecheckAlarmIntent(final String pkgName) {
         final Intent intent = new Intent(ACTION_PROVISIONING_ALARM);
+        intent.setPackage(pkgName);
         return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
     }
 
@@ -470,7 +471,7 @@
             final int period = config.provisioningCheckPeriod;
             if (period <= 0) return;
 
-            mProvisioningRecheckAlarm = createRecheckAlarmIntent();
+            mProvisioningRecheckAlarm = createRecheckAlarmIntent(mContext.getPackageName());
             AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(
                     Context.ALARM_SERVICE);
             long triggerAtMillis = SystemClock.elapsedRealtime() + (period * MS_PER_HOUR);
diff --git a/Tethering/src/com/android/networkstack/tethering/IOffloadHal.java b/Tethering/src/com/android/networkstack/tethering/IOffloadHal.java
new file mode 100644
index 0000000..e66e7ae
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/IOffloadHal.java
@@ -0,0 +1,64 @@
+/*
+ * 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 com.android.networkstack.tethering;
+
+import android.annotation.NonNull;
+import android.os.NativeHandle;
+
+import com.android.networkstack.tethering.OffloadHardwareInterface.ForwardedStats;
+import com.android.networkstack.tethering.OffloadHardwareInterface.OffloadHalCallback;
+
+import java.util.ArrayList;
+
+/** Abstraction of Tetheroffload HAL interface */
+interface IOffloadHal {
+    /*
+     * Initialize the Tetheroffload HAL. Offload management process need to know conntrack rules to
+     * support NAT, but it may not have permission to create netlink netfilter sockets. Create two
+     * netlink netfilter sockets and share them with offload management process.
+     */
+    boolean initOffload(@NonNull NativeHandle handle1, @NonNull NativeHandle handle2,
+            @NonNull OffloadHalCallback callback);
+
+    /** Stop the Tetheroffload HAL. */
+    boolean stopOffload();
+
+    /** Get HAL interface version number. */
+    int getVersion();
+
+    /** Get Tx/Rx usage from last query. */
+    ForwardedStats getForwardedStats(@NonNull String upstream);
+
+    /** Set local prefixes to offload management process. */
+    boolean setLocalPrefixes(@NonNull ArrayList<String> localPrefixes);
+
+    /** Set data limit value to offload management process. */
+    boolean setDataLimit(@NonNull String iface, long limit);
+
+    /** Set data warning and limit value to offload management process. */
+    boolean setDataWarningAndLimit(@NonNull String iface, long warning, long limit);
+
+    /** Set upstream parameters to offload management process. */
+    boolean setUpstreamParameters(@NonNull String iface, @NonNull String v4addr,
+            @NonNull String v4gateway, @NonNull ArrayList<String> v6gws);
+
+    /** Add downstream prefix to offload management process. */
+    boolean addDownstream(@NonNull String ifname, @NonNull String prefix);
+
+    /** Remove downstream prefix from offload management process. */
+    boolean removeDownstream(@NonNull String ifname, @NonNull String prefix);
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadController.java b/Tethering/src/com/android/networkstack/tethering/OffloadController.java
index 94684af..b4c0d6a 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadController.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadController.java
@@ -26,8 +26,8 @@
 import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
 import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
 
-import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0;
-import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_1;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_0;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_1;
 import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_NONE;
 import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
 
@@ -55,7 +55,7 @@
 import com.android.net.module.util.SharedLog;
 import com.android.net.module.util.netlink.ConntrackMessage;
 import com.android.net.module.util.netlink.NetlinkConstants;
-import com.android.net.module.util.netlink.NetlinkSocket;
+import com.android.net.module.util.netlink.NetlinkUtils;
 import com.android.networkstack.tethering.OffloadHardwareInterface.ForwardedStats;
 
 import java.net.Inet4Address;
@@ -98,9 +98,8 @@
     private final OffloadTetheringStatsProvider mStatsProvider;
     private final SharedLog mLog;
     private final HashMap<String, LinkProperties> mDownstreams;
-    private boolean mConfigInitialized;
     @OffloadHardwareInterface.OffloadHalVersion
-    private int mControlHalVersion;
+    private int mOffloadHalVersion;
     private LinkProperties mUpstreamLinkProperties;
     // The complete set of offload-exempt prefixes passed in via Tethering from
     // all upstream and downstream sources.
@@ -205,20 +204,11 @@
             return false;
         }
 
-        if (!mConfigInitialized) {
-            mConfigInitialized = mHwInterface.initOffloadConfig();
-            if (!mConfigInitialized) {
-                mLog.i("tethering offload config not supported");
-                stop();
-                return false;
-            }
-        }
-
-        mControlHalVersion = mHwInterface.initOffloadControl(
+        mOffloadHalVersion = mHwInterface.initOffload(
                 // OffloadHardwareInterface guarantees that these callback
                 // methods are called on the handler passed to it, which is the
                 // same as mHandler, as coordinated by the setup in Tethering.
-                new OffloadHardwareInterface.ControlCallback() {
+                new OffloadHardwareInterface.OffloadHalCallback() {
                     @Override
                     public void onStarted() {
                         if (!started()) return;
@@ -305,11 +295,11 @@
 
         final boolean isStarted = started();
         if (!isStarted) {
-            mLog.i("tethering offload control not supported");
+            mLog.i("tethering offload not supported");
             stop();
         } else {
             mLog.log("tethering offload started, version: "
-                    + OffloadHardwareInterface.halVerToString(mControlHalVersion));
+                    + OffloadHardwareInterface.halVerToString(mOffloadHalVersion));
             mNatUpdateCallbacksReceived = 0;
             mNatUpdateNetlinkErrors = 0;
             maybeSchedulePollingStats();
@@ -325,9 +315,8 @@
         final boolean wasStarted = started();
         updateStatsForCurrentUpstream();
         mUpstreamLinkProperties = null;
-        mHwInterface.stopOffloadControl();
-        mControlHalVersion = OFFLOAD_HAL_VERSION_NONE;
-        mConfigInitialized = false;
+        mHwInterface.stopOffload();
+        mOffloadHalVersion = OFFLOAD_HAL_VERSION_NONE;
         if (mHandler.hasCallbacks(mScheduledPollingTask)) {
             mHandler.removeCallbacks(mScheduledPollingTask);
         }
@@ -335,7 +324,7 @@
     }
 
     private boolean started() {
-        return mConfigInitialized && mControlHalVersion != OFFLOAD_HAL_VERSION_NONE;
+        return mOffloadHalVersion != OFFLOAD_HAL_VERSION_NONE;
     }
 
     @VisibleForTesting
@@ -528,7 +517,7 @@
     }
 
     private boolean useStatsPolling() {
-        return mControlHalVersion == OFFLOAD_HAL_VERSION_1_0;
+        return mOffloadHalVersion == OFFLOAD_HAL_VERSION_HIDL_1_0;
     }
 
     private boolean maybeUpdateDataWarningAndLimit(String iface) {
@@ -540,7 +529,7 @@
 
         final InterfaceQuota quota = mInterfaceQuotas.getOrDefault(iface, InterfaceQuota.MAX_VALUE);
         final boolean ret;
-        if (mControlHalVersion >= OFFLOAD_HAL_VERSION_1_1) {
+        if (mOffloadHalVersion >= OFFLOAD_HAL_VERSION_HIDL_1_1) {
             ret = mHwInterface.setDataWarningAndLimit(iface, quota.warningBytes, quota.limitBytes);
         } else {
             ret = mHwInterface.setDataLimit(iface, quota.limitBytes);
@@ -611,7 +600,7 @@
         for (RouteInfo ri : oldRoutes) {
             if (shouldIgnoreDownstreamRoute(ri)) continue;
             if (!newRoutes.contains(ri)) {
-                mHwInterface.removeDownstreamPrefix(ifname, ri.getDestination().toString());
+                mHwInterface.removeDownstream(ifname, ri.getDestination().toString());
             }
         }
 
@@ -619,7 +608,7 @@
         for (RouteInfo ri : newRoutes) {
             if (shouldIgnoreDownstreamRoute(ri)) continue;
             if (!oldRoutes.contains(ri)) {
-                mHwInterface.addDownstreamPrefix(ifname, ri.getDestination().toString());
+                mHwInterface.addDownstream(ifname, ri.getDestination().toString());
             }
         }
     }
@@ -639,7 +628,7 @@
 
         for (RouteInfo route : lp.getRoutes()) {
             if (shouldIgnoreDownstreamRoute(route)) continue;
-            mHwInterface.removeDownstreamPrefix(ifname, route.getDestination().toString());
+            mHwInterface.removeDownstream(ifname, route.getDestination().toString());
         }
     }
 
@@ -768,11 +757,21 @@
         final boolean isStarted = started();
         pw.println("Offload HALs " + (isStarted ? "started" : "not started"));
         pw.println("Offload Control HAL version: "
-                + OffloadHardwareInterface.halVerToString(mControlHalVersion));
+                + OffloadHardwareInterface.halVerToString(mOffloadHalVersion));
         LinkProperties lp = mUpstreamLinkProperties;
         String upstream = (lp != null) ? lp.getInterfaceName() : null;
         pw.println("Current upstream: " + upstream);
         pw.println("Exempt prefixes: " + mLastLocalPrefixStrs);
+        pw.println("ForwardedStats:");
+        pw.increaseIndent();
+        if (mForwardedStats.isEmpty()) {
+            pw.println("<empty>");
+        } else {
+            for (final Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) {
+                pw.println(kv.getKey() + ": " + kv.getValue());
+            }
+        }
+        pw.decreaseIndent();
         pw.println("NAT timeout update callbacks received during the "
                 + (isStarted ? "current" : "last")
                 + " offload session: "
@@ -825,7 +824,7 @@
                 proto, src, srcPort, dst, dstPort, timeoutSec);
 
         try {
-            NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_NETFILTER, msg);
+            NetlinkUtils.sendOneShotKernelMessage(OsConstants.NETLINK_NETFILTER, msg);
         } catch (ErrnoException e) {
             mNatUpdateNetlinkErrors++;
             mLog.e("Error updating NAT conntrack entry >" + natDescription + "<: " + e
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHalAidlImpl.java b/Tethering/src/com/android/networkstack/tethering/OffloadHalAidlImpl.java
new file mode 100644
index 0000000..e7dc757
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadHalAidlImpl.java
@@ -0,0 +1,304 @@
+/*
+ * 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 com.android.networkstack.tethering;
+
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_AIDL;
+
+import android.annotation.NonNull;
+import android.hardware.tetheroffload.ForwardedStats;
+import android.hardware.tetheroffload.IOffload;
+import android.hardware.tetheroffload.ITetheringOffloadCallback;
+import android.hardware.tetheroffload.NatTimeoutUpdate;
+import android.hardware.tetheroffload.NetworkProtocol;
+import android.hardware.tetheroffload.OffloadCallbackEvent;
+import android.os.Handler;
+import android.os.NativeHandle;
+import android.os.ParcelFileDescriptor;
+import android.os.ServiceManager;
+import android.system.OsConstants;
+
+import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.SharedLog;
+import com.android.networkstack.tethering.OffloadHardwareInterface.OffloadHalCallback;
+
+import java.util.ArrayList;
+
+/**
+ * The implementation of IOffloadHal which based on Stable AIDL interface
+ */
+public class OffloadHalAidlImpl implements IOffloadHal {
+    private static final String TAG = OffloadHalAidlImpl.class.getSimpleName();
+    private static final String HAL_INSTANCE_NAME = IOffload.DESCRIPTOR + "/default";
+
+    private final Handler mHandler;
+    private final SharedLog mLog;
+    private final IOffload mIOffload;
+    @OffloadHardwareInterface.OffloadHalVersion
+    private final int mOffloadVersion;
+
+    private TetheringOffloadCallback mTetheringOffloadCallback;
+
+    public OffloadHalAidlImpl(int version, @NonNull IOffload offload, @NonNull Handler handler,
+            @NonNull SharedLog log) {
+        mOffloadVersion = version;
+        mIOffload = offload;
+        mHandler = handler;
+        mLog = log.forSubComponent(TAG);
+    }
+
+    /**
+     * Initialize the Tetheroffload HAL. Provides bound netlink file descriptors for use in the
+     * management process.
+     */
+    public boolean initOffload(@NonNull NativeHandle handle1, @NonNull NativeHandle handle2,
+            @NonNull OffloadHalCallback callback) {
+        final String methodStr = String.format("initOffload(%d, %d, %s)",
+                handle1.getFileDescriptor().getInt$(), handle2.getFileDescriptor().getInt$(),
+                (callback == null) ? "null"
+                : "0x" + Integer.toHexString(System.identityHashCode(callback)));
+        mTetheringOffloadCallback = new TetheringOffloadCallback(mHandler, callback, mLog);
+        try {
+            mIOffload.initOffload(
+                    ParcelFileDescriptor.adoptFd(handle1.getFileDescriptor().getInt$()),
+                    ParcelFileDescriptor.adoptFd(handle2.getFileDescriptor().getInt$()),
+                    mTetheringOffloadCallback);
+        } catch (Exception e) {
+            logAndIgnoreException(e, methodStr);
+            return false;
+        }
+        mLog.i(methodStr);
+        return true;
+    }
+
+    /** Stop the Tetheroffload HAL. */
+    public boolean stopOffload() {
+        final String methodStr = "stopOffload()";
+        try {
+            mIOffload.stopOffload();
+        } catch (Exception e) {
+            logAndIgnoreException(e, methodStr);
+            return false;
+        }
+
+        mTetheringOffloadCallback = null;
+        mLog.i(methodStr);
+        return true;
+    }
+
+    /** Get HAL interface version number. */
+    public int getVersion() {
+        return mOffloadVersion;
+    }
+
+    /** Get Tx/Rx usage from last query. */
+    public OffloadHardwareInterface.ForwardedStats getForwardedStats(@NonNull String upstream) {
+        ForwardedStats stats = new ForwardedStats();
+        final String methodStr = String.format("getForwardedStats(%s)",  upstream);
+        try {
+            stats = mIOffload.getForwardedStats(upstream);
+        } catch (Exception e) {
+            logAndIgnoreException(e, methodStr);
+        }
+        mLog.i(methodStr);
+        return new OffloadHardwareInterface.ForwardedStats(stats.rxBytes, stats.txBytes);
+    }
+
+    /** Set local prefixes to offload management process. */
+    public boolean setLocalPrefixes(@NonNull ArrayList<String> localPrefixes) {
+        final String methodStr = String.format("setLocalPrefixes([%s])",
+                String.join(",", localPrefixes));
+        try {
+            mIOffload.setLocalPrefixes(localPrefixes.toArray(new String[localPrefixes.size()]));
+        } catch (Exception e) {
+            logAndIgnoreException(e, methodStr);
+            return false;
+        }
+        mLog.i(methodStr);
+        return true;
+    }
+
+    /**
+     * Set data limit value to offload management process.
+     * Method setDataLimit is deprecated in AIDL, so call setDataWarningAndLimit instead,
+     * with warningBytes set to its MAX_VALUE.
+     */
+    public boolean setDataLimit(@NonNull String iface, long limit) {
+        final long warning = Long.MAX_VALUE;
+        final String methodStr = String.format("setDataLimit(%s, %d)", iface, limit);
+        try {
+            mIOffload.setDataWarningAndLimit(iface, warning, limit);
+        } catch (Exception e) {
+            logAndIgnoreException(e, methodStr);
+            return false;
+        }
+        mLog.i(methodStr);
+        return true;
+    }
+
+    /** Set data warning and limit value to offload management process. */
+    public boolean setDataWarningAndLimit(@NonNull String iface, long warning, long limit) {
+        final String methodStr =
+                String.format("setDataWarningAndLimit(%s, %d, %d)", iface, warning, limit);
+        try {
+            mIOffload.setDataWarningAndLimit(iface, warning, limit);
+        } catch (Exception e) {
+            logAndIgnoreException(e, methodStr);
+            return false;
+        }
+        mLog.i(methodStr);
+        return true;
+    }
+
+    /** Set upstream parameters to offload management process. */
+    public boolean setUpstreamParameters(@NonNull String iface, @NonNull String v4addr,
+            @NonNull String v4gateway, @NonNull ArrayList<String> v6gws) {
+        final String methodStr = String.format("setUpstreamParameters(%s, %s, %s, [%s])",
+                iface, v4addr, v4gateway, String.join(",", v6gws));
+        try {
+            mIOffload.setUpstreamParameters(iface, v4addr, v4gateway,
+                    v6gws.toArray(new String[v6gws.size()]));
+        } catch (Exception e) {
+            logAndIgnoreException(e, methodStr);
+            return false;
+        }
+        mLog.i(methodStr);
+        return true;
+    }
+
+    /** Add downstream prefix to offload management process. */
+    public boolean addDownstream(@NonNull String ifname, @NonNull String prefix) {
+        final String methodStr = String.format("addDownstream(%s, %s)", ifname, prefix);
+        try {
+            mIOffload.addDownstream(ifname, prefix);
+        } catch (Exception e) {
+            logAndIgnoreException(e, methodStr);
+            return false;
+        }
+        mLog.i(methodStr);
+        return true;
+    }
+
+    /** Remove downstream prefix from offload management process. */
+    public boolean removeDownstream(@NonNull String ifname, @NonNull String prefix) {
+        final String methodStr = String.format("removeDownstream(%s, %s)", ifname, prefix);
+        try {
+            mIOffload.removeDownstream(ifname, prefix);
+        } catch (Exception e) {
+            logAndIgnoreException(e, methodStr);
+            return false;
+        }
+        mLog.i(methodStr);
+        return true;
+    }
+
+    /**
+     * Get {@link IOffloadHal} object from the AIDL service.
+     *
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     * @param log Log to be used by the repository.
+     */
+    public static IOffloadHal getIOffloadHal(Handler handler, SharedLog log) {
+        // Tetheroffload AIDL interface is only supported after U.
+        if (!SdkLevel.isAtLeastU() || !ServiceManager.isDeclared(HAL_INSTANCE_NAME)) return null;
+
+        IOffload offload = IOffload.Stub.asInterface(
+                ServiceManager.waitForDeclaredService(HAL_INSTANCE_NAME));
+        if (offload == null) return null;
+
+        return new OffloadHalAidlImpl(OFFLOAD_HAL_VERSION_AIDL, offload, handler, log);
+    }
+
+    private void logAndIgnoreException(Exception e, final String methodStr) {
+        mLog.e(methodStr + " failed with " + e.getClass().getSimpleName() + ": ", e);
+    }
+
+    private static class TetheringOffloadCallback extends ITetheringOffloadCallback.Stub {
+        public final Handler handler;
+        public final OffloadHalCallback callback;
+        public final SharedLog log;
+
+        TetheringOffloadCallback(
+                Handler h, OffloadHalCallback cb, SharedLog sharedLog) {
+            handler = h;
+            callback = cb;
+            log = sharedLog;
+        }
+
+        private void handleOnEvent(int event) {
+            switch (event) {
+                case OffloadCallbackEvent.OFFLOAD_STARTED:
+                    callback.onStarted();
+                    break;
+                case OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR:
+                    callback.onStoppedError();
+                    break;
+                case OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED:
+                    callback.onStoppedUnsupported();
+                    break;
+                case OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE:
+                    callback.onSupportAvailable();
+                    break;
+                case OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED:
+                    callback.onStoppedLimitReached();
+                    break;
+                case OffloadCallbackEvent.OFFLOAD_WARNING_REACHED:
+                    callback.onWarningReached();
+                    break;
+                default:
+                    log.e("Unsupported OffloadCallbackEvent: " + event);
+            }
+        }
+
+        @Override
+        public void onEvent(int event) {
+            handler.post(() -> {
+                handleOnEvent(event);
+            });
+        }
+
+        @Override
+        public void updateTimeout(NatTimeoutUpdate params) {
+            handler.post(() -> {
+                callback.onNatTimeoutUpdate(
+                        networkProtocolToOsConstant(params.proto),
+                        params.src.addr, params.src.port,
+                        params.dst.addr, params.dst.port);
+            });
+        }
+
+        @Override
+        public String getInterfaceHash() {
+            return ITetheringOffloadCallback.HASH;
+        }
+
+        @Override
+        public int getInterfaceVersion() {
+            return ITetheringOffloadCallback.VERSION;
+        }
+    }
+
+    private static int networkProtocolToOsConstant(int proto) {
+        switch (proto) {
+            case NetworkProtocol.TCP: return OsConstants.IPPROTO_TCP;
+            case NetworkProtocol.UDP: return OsConstants.IPPROTO_UDP;
+            default:
+                // The caller checks this value and will log an error. Just make
+                // sure it won't collide with valid OsConstants.IPPROTO_* values.
+                return -Math.abs(proto);
+        }
+    }
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHalHidlImpl.java b/Tethering/src/com/android/networkstack/tethering/OffloadHalHidlImpl.java
new file mode 100644
index 0000000..e0a9878
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadHalHidlImpl.java
@@ -0,0 +1,439 @@
+/*
+ * 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 com.android.networkstack.tethering;
+
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_0;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_1;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_NONE;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.halVerToString;
+import static com.android.networkstack.tethering.util.TetheringUtils.uint16;
+
+import android.annotation.NonNull;
+import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
+import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
+import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
+import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
+import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
+import android.hardware.tetheroffload.control.V1_1.ITetheringOffloadCallback;
+import android.os.Handler;
+import android.os.NativeHandle;
+import android.os.RemoteException;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.android.net.module.util.SharedLog;
+import com.android.networkstack.tethering.OffloadHardwareInterface.ForwardedStats;
+import com.android.networkstack.tethering.OffloadHardwareInterface.OffloadHalCallback;
+
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+
+/**
+ * The implementation of IOffloadHal which based on HIDL interfaces
+ */
+public class OffloadHalHidlImpl implements IOffloadHal {
+    private static final String TAG = OffloadHalHidlImpl.class.getSimpleName();
+    private static final String YIELDS = " -> ";
+
+    private final Handler mHandler;
+    private final SharedLog mLog;
+    private final IOffloadConfig mIOffloadConfig;
+    private final IOffloadControl mIOffloadControl;
+    @OffloadHardwareInterface.OffloadHalVersion
+    private final int mOffloadControlVersion;
+
+    private OffloadHalCallback mOffloadHalCallback;
+    private TetheringOffloadCallback mTetheringOffloadCallback;
+
+    public OffloadHalHidlImpl(int version, @NonNull IOffloadConfig config,
+            @NonNull IOffloadControl control, @NonNull Handler handler, @NonNull SharedLog log) {
+        mOffloadControlVersion = version;
+        mIOffloadConfig = config;
+        mIOffloadControl = control;
+        mHandler = handler;
+        mLog = log.forSubComponent(TAG);
+    }
+
+    /**
+     * Initialize the Tetheroffload HAL. Provides bound netlink file descriptors for use in the
+     * management process.
+     */
+    public boolean initOffload(@NonNull NativeHandle handle1, @NonNull NativeHandle handle2,
+            @NonNull OffloadHalCallback callback) {
+        final String logmsg = String.format("initOffload(%d, %d, %s)",
+                handle1.getFileDescriptor().getInt$(), handle2.getFileDescriptor().getInt$(),
+                (callback == null) ? "null"
+                : "0x" + Integer.toHexString(System.identityHashCode(callback)));
+
+        mOffloadHalCallback = callback;
+        mTetheringOffloadCallback = new TetheringOffloadCallback(
+                mHandler, mOffloadHalCallback, mLog, mOffloadControlVersion);
+        final CbResults results = new CbResults();
+        try {
+            mIOffloadConfig.setHandles(handle1, handle2,
+                    (boolean success, String errMsg) -> {
+                        results.mSuccess = success;
+                        results.mErrMsg = errMsg;
+                    });
+            mIOffloadControl.initOffload(
+                    mTetheringOffloadCallback,
+                    (boolean success, String errMsg) -> {
+                        results.mSuccess = success;
+                        results.mErrMsg = errMsg;
+                    });
+        } catch (RemoteException e) {
+            record(logmsg, e);
+            return false;
+        }
+
+        record(logmsg, results);
+        return results.mSuccess;
+    }
+
+    /** Stop the Tetheroffload HAL. */
+    public boolean stopOffload() {
+        try {
+            mIOffloadControl.stopOffload(
+                    (boolean success, String errMsg) -> {
+                        if (!success) mLog.e("stopOffload failed: " + errMsg);
+                    });
+        } catch (RemoteException e) {
+            mLog.e("failed to stopOffload: " + e);
+        }
+        mOffloadHalCallback = null;
+        mTetheringOffloadCallback = null;
+        mLog.log("stopOffload()");
+        return true;
+    }
+
+    /** Get HAL interface version number. */
+    public int getVersion() {
+        return mOffloadControlVersion;
+    }
+
+    /** Get Tx/Rx usage from last query. */
+    public ForwardedStats getForwardedStats(@NonNull String upstream) {
+        final String logmsg = String.format("getForwardedStats(%s)",  upstream);
+
+        final ForwardedStats stats = new ForwardedStats();
+        try {
+            mIOffloadControl.getForwardedStats(
+                    upstream,
+                    (long rxBytes, long txBytes) -> {
+                        stats.rxBytes = (rxBytes > 0) ? rxBytes : 0;
+                        stats.txBytes = (txBytes > 0) ? txBytes : 0;
+                    });
+        } catch (RemoteException e) {
+            record(logmsg, e);
+            return stats;
+        }
+
+        return stats;
+    }
+
+    /** Set local prefixes to offload management process. */
+    public boolean setLocalPrefixes(@NonNull ArrayList<String> localPrefixes) {
+        final String logmsg = String.format("setLocalPrefixes([%s])",
+                String.join(",", localPrefixes));
+
+        final CbResults results = new CbResults();
+        try {
+            mIOffloadControl.setLocalPrefixes(localPrefixes,
+                    (boolean success, String errMsg) -> {
+                        results.mSuccess = success;
+                        results.mErrMsg = errMsg;
+                    });
+        } catch (RemoteException e) {
+            record(logmsg, e);
+            return false;
+        }
+
+        record(logmsg, results);
+        return results.mSuccess;
+    }
+
+    /** Set data limit value to offload management process. */
+    public boolean setDataLimit(@NonNull String iface, long limit) {
+
+        final String logmsg = String.format("setDataLimit(%s, %d)", iface, limit);
+
+        final CbResults results = new CbResults();
+        try {
+            mIOffloadControl.setDataLimit(
+                    iface, limit,
+                    (boolean success, String errMsg) -> {
+                        results.mSuccess = success;
+                        results.mErrMsg = errMsg;
+                    });
+        } catch (RemoteException e) {
+            record(logmsg, e);
+            return false;
+        }
+
+        record(logmsg, results);
+        return results.mSuccess;
+    }
+
+    /** Set data warning and limit value to offload management process. */
+    public boolean setDataWarningAndLimit(@NonNull String iface, long warning, long limit) {
+        if (mOffloadControlVersion < OFFLOAD_HAL_VERSION_HIDL_1_1) {
+            throw new UnsupportedOperationException(
+                    "setDataWarningAndLimit is not supported below HAL V1.1");
+        }
+        final String logmsg =
+                String.format("setDataWarningAndLimit(%s, %d, %d)", iface, warning, limit);
+
+        final CbResults results = new CbResults();
+        try {
+            ((android.hardware.tetheroffload.control.V1_1.IOffloadControl) mIOffloadControl)
+                    .setDataWarningAndLimit(
+                            iface, warning, limit,
+                            (boolean success, String errMsg) -> {
+                                results.mSuccess = success;
+                                results.mErrMsg = errMsg;
+                            });
+        } catch (RemoteException e) {
+            record(logmsg, e);
+            return false;
+        }
+
+        record(logmsg, results);
+        return results.mSuccess;
+    }
+
+    /** Set upstream parameters to offload management process. */
+    public boolean setUpstreamParameters(@NonNull String iface, @NonNull String v4addr,
+            @NonNull String v4gateway, @NonNull ArrayList<String> v6gws) {
+        final String logmsg = String.format("setUpstreamParameters(%s, %s, %s, [%s])",
+                iface, v4addr, v4gateway, String.join(",", v6gws));
+
+        final CbResults results = new CbResults();
+        try {
+            mIOffloadControl.setUpstreamParameters(
+                    iface, v4addr, v4gateway, v6gws,
+                    (boolean success, String errMsg) -> {
+                        results.mSuccess = success;
+                        results.mErrMsg = errMsg;
+                    });
+        } catch (RemoteException e) {
+            record(logmsg, e);
+            return false;
+        }
+
+        record(logmsg, results);
+        return results.mSuccess;
+    }
+
+    /** Add downstream prefix to offload management process. */
+    public boolean addDownstream(@NonNull String ifname, @NonNull String prefix) {
+        final String logmsg = String.format("addDownstream(%s, %s)", ifname, prefix);
+
+        final CbResults results = new CbResults();
+        try {
+            mIOffloadControl.addDownstream(ifname, prefix,
+                    (boolean success, String errMsg) -> {
+                        results.mSuccess = success;
+                        results.mErrMsg = errMsg;
+                    });
+        } catch (RemoteException e) {
+            record(logmsg, e);
+            return false;
+        }
+
+        record(logmsg, results);
+        return results.mSuccess;
+    }
+
+    /** Remove downstream prefix from offload management process. */
+    public boolean removeDownstream(@NonNull String ifname, @NonNull String prefix) {
+        final String logmsg = String.format("removeDownstream(%s, %s)", ifname, prefix);
+
+        final CbResults results = new CbResults();
+        try {
+            mIOffloadControl.removeDownstream(ifname, prefix,
+                    (boolean success, String errMsg) -> {
+                        results.mSuccess = success;
+                        results.mErrMsg = errMsg;
+                    });
+        } catch (RemoteException e) {
+            record(logmsg, e);
+            return false;
+        }
+
+        record(logmsg, results);
+        return results.mSuccess;
+    }
+
+    /**
+     * Get {@link IOffloadHal} object from the HIDL service.
+     *
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     * @param log Log to be used by the repository.
+     */
+    public static IOffloadHal getIOffloadHal(Handler handler, SharedLog log) {
+        IOffloadConfig config = null;
+        try {
+            config = IOffloadConfig.getService(true /*retry*/);
+        } catch (RemoteException e) {
+            log.e("getIOffloadConfig error " + e);
+            return null;
+        } catch (NoSuchElementException e) {
+            log.i("getIOffloadConfig Tether Offload HAL not present/implemented");
+            return null;
+        }
+
+        IOffloadControl control = null;
+        int version = OFFLOAD_HAL_VERSION_NONE;
+        try {
+            control = android.hardware.tetheroffload.control
+                    .V1_1.IOffloadControl.getService(true /*retry*/);
+            version = OFFLOAD_HAL_VERSION_HIDL_1_1;
+        } catch (NoSuchElementException e) {
+            // Unsupported by device.
+        } catch (RemoteException e) {
+            log.e("Unable to get offload control " + OFFLOAD_HAL_VERSION_HIDL_1_1);
+        }
+        if (control == null) {
+            try {
+                control = IOffloadControl.getService(true /*retry*/);
+                version = OFFLOAD_HAL_VERSION_HIDL_1_0;
+            } catch (NoSuchElementException e) {
+                // Unsupported by device.
+            } catch (RemoteException e) {
+                log.e("Unable to get offload control " + OFFLOAD_HAL_VERSION_HIDL_1_0);
+            }
+        }
+
+        if (config == null || control == null) return null;
+
+        return new OffloadHalHidlImpl(version, config, control, handler, log);
+    }
+
+    private void record(String msg, Throwable t) {
+        mLog.e(msg + YIELDS + "exception: " + t);
+    }
+
+    private void record(String msg, CbResults results) {
+        final String logmsg = msg + YIELDS + results;
+        if (!results.mSuccess) {
+            mLog.e(logmsg);
+        } else {
+            mLog.log(logmsg);
+        }
+    }
+
+    private static class TetheringOffloadCallback extends ITetheringOffloadCallback.Stub {
+        public final Handler handler;
+        public final OffloadHalCallback callback;
+        public final SharedLog log;
+        private final int mOffloadControlVersion;
+
+        TetheringOffloadCallback(
+                Handler h, OffloadHalCallback cb, SharedLog sharedLog, int offloadControlVersion) {
+            handler = h;
+            callback = cb;
+            log = sharedLog;
+            this.mOffloadControlVersion = offloadControlVersion;
+        }
+
+        private void handleOnEvent(int event) {
+            switch (event) {
+                case OffloadCallbackEvent.OFFLOAD_STARTED:
+                    callback.onStarted();
+                    break;
+                case OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR:
+                    callback.onStoppedError();
+                    break;
+                case OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED:
+                    callback.onStoppedUnsupported();
+                    break;
+                case OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE:
+                    callback.onSupportAvailable();
+                    break;
+                case OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED:
+                    callback.onStoppedLimitReached();
+                    break;
+                case android.hardware.tetheroffload.control
+                        .V1_1.OffloadCallbackEvent.OFFLOAD_WARNING_REACHED:
+                    callback.onWarningReached();
+                    break;
+                default:
+                    log.e("Unsupported OffloadCallbackEvent: " + event);
+            }
+        }
+
+        @Override
+        public void onEvent(int event) {
+            // The implementation should never call onEvent()) if the event is already reported
+            // through newer callback.
+            if (mOffloadControlVersion > OFFLOAD_HAL_VERSION_HIDL_1_0) {
+                Log.wtf(TAG, "onEvent(" + event + ") fired on HAL "
+                        + halVerToString(mOffloadControlVersion));
+            }
+            handler.post(() -> {
+                handleOnEvent(event);
+            });
+        }
+
+        @Override
+        public void onEvent_1_1(int event) {
+            if (mOffloadControlVersion < OFFLOAD_HAL_VERSION_HIDL_1_1) {
+                Log.wtf(TAG, "onEvent_1_1(" + event + ") fired on HAL "
+                        + halVerToString(mOffloadControlVersion));
+                return;
+            }
+            handler.post(() -> {
+                handleOnEvent(event);
+            });
+        }
+
+        @Override
+        public void updateTimeout(NatTimeoutUpdate params) {
+            handler.post(() -> {
+                callback.onNatTimeoutUpdate(
+                        networkProtocolToOsConstant(params.proto),
+                        params.src.addr, uint16(params.src.port),
+                        params.dst.addr, uint16(params.dst.port));
+            });
+        }
+    }
+
+    private static int networkProtocolToOsConstant(int proto) {
+        switch (proto) {
+            case NetworkProtocol.TCP: return OsConstants.IPPROTO_TCP;
+            case NetworkProtocol.UDP: return OsConstants.IPPROTO_UDP;
+            default:
+                // The caller checks this value and will log an error. Just make
+                // sure it won't collide with valid OsConstants.IPPROTO_* values.
+                return -Math.abs(proto);
+        }
+    }
+
+    private static class CbResults {
+        boolean mSuccess;
+        String mErrMsg;
+
+        @Override
+        public String toString() {
+            if (mSuccess) {
+                return "ok";
+            } else {
+                return "fail: " + mErrMsg;
+            }
+        }
+    }
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index 846abcb..de15c5b 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -18,29 +18,19 @@
 
 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
-import static com.android.networkstack.tethering.util.TetheringUtils.uint16;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
-import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
-import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
-import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
-import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
-import android.hardware.tetheroffload.control.V1_1.ITetheringOffloadCallback;
 import android.net.util.SocketUtils;
 import android.os.Handler;
 import android.os.NativeHandle;
-import android.os.RemoteException;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
-import android.util.Log;
-import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.net.module.util.SharedLog;
-import com.android.net.module.util.netlink.NetlinkSocket;
+import com.android.net.module.util.netlink.NetlinkUtils;
 import com.android.net.module.util.netlink.StructNfGenMsg;
 import com.android.net.module.util.netlink.StructNlMsgHdr;
 
@@ -54,8 +44,6 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
-import java.util.NoSuchElementException;
-
 
 /**
  * Capture tethering dependencies, for injection.
@@ -86,43 +74,43 @@
     private final Handler mHandler;
     private final SharedLog mLog;
     private final Dependencies mDeps;
-    private IOffloadControl mOffloadControl;
+    private IOffloadHal mIOffload;
 
     // TODO: Use major-minor version control to prevent from defining new constants.
     static final int OFFLOAD_HAL_VERSION_NONE = 0;
-    static final int OFFLOAD_HAL_VERSION_1_0 = 1;
-    static final int OFFLOAD_HAL_VERSION_1_1 = 2;
+    static final int OFFLOAD_HAL_VERSION_HIDL_1_0 = 1;
+    static final int OFFLOAD_HAL_VERSION_HIDL_1_1 = 2;
+    static final int OFFLOAD_HAL_VERSION_AIDL = 3;
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "OFFLOAD_HAL_VERSION_", value = {
             OFFLOAD_HAL_VERSION_NONE,
-            OFFLOAD_HAL_VERSION_1_0,
-            OFFLOAD_HAL_VERSION_1_1
+            OFFLOAD_HAL_VERSION_HIDL_1_0,
+            OFFLOAD_HAL_VERSION_HIDL_1_1,
+            OFFLOAD_HAL_VERSION_AIDL,
     })
     public @interface OffloadHalVersion {}
-    @OffloadHalVersion
-    private int mOffloadControlVersion = OFFLOAD_HAL_VERSION_NONE;
 
     @NonNull
     static String halVerToString(int version) {
         switch(version) {
-            case OFFLOAD_HAL_VERSION_1_0:
-                return "1.0";
-            case OFFLOAD_HAL_VERSION_1_1:
-                return "1.1";
+            case OFFLOAD_HAL_VERSION_HIDL_1_0:
+                return "HIDL 1.0";
+            case OFFLOAD_HAL_VERSION_HIDL_1_1:
+                return "HIDL 1.1";
+            case OFFLOAD_HAL_VERSION_AIDL:
+                return "AIDL";
             case OFFLOAD_HAL_VERSION_NONE:
                 return "None";
             default:
                 throw new IllegalArgumentException("Unsupported version int " + version);
         }
-
     }
 
-    private TetheringOffloadCallback mTetheringOffloadCallback;
-    private ControlCallback mControlCallback;
+    private OffloadHalCallback mOffloadHalCallback;
 
     /** The callback to notify status of offload management process. */
-    public static class ControlCallback {
+    public static class OffloadHalCallback {
         /** Offload started. */
         public void onStarted() {}
         /**
@@ -179,7 +167,7 @@
     }
 
     public OffloadHardwareInterface(Handler h, SharedLog log) {
-        this(h, log, new Dependencies(log));
+        this(h, log, new Dependencies(h, log));
     }
 
     OffloadHardwareInterface(Handler h, SharedLog log, Dependencies deps) {
@@ -190,51 +178,27 @@
 
     /** Capture OffloadHardwareInterface dependencies, for injection. */
     static class Dependencies {
+        private final Handler mHandler;
         private final SharedLog mLog;
 
-        Dependencies(SharedLog log) {
+        Dependencies(Handler handler, SharedLog log) {
+            mHandler = handler;
             mLog = log;
         }
 
-        public IOffloadConfig getOffloadConfig() {
-            try {
-                return IOffloadConfig.getService(true /*retry*/);
-            } catch (RemoteException | NoSuchElementException e) {
-                mLog.e("getIOffloadConfig error " + e);
-                return null;
-            }
-        }
-
-        @NonNull
-        public Pair<IOffloadControl, Integer> getOffloadControl() {
-            IOffloadControl hal = null;
-            int version = OFFLOAD_HAL_VERSION_NONE;
-            try {
-                hal = android.hardware.tetheroffload.control
-                        .V1_1.IOffloadControl.getService(true /*retry*/);
-                version = OFFLOAD_HAL_VERSION_1_1;
-            } catch (NoSuchElementException e) {
-                // Unsupported by device.
-            } catch (RemoteException e) {
-                mLog.e("Unable to get offload control " + OFFLOAD_HAL_VERSION_1_1);
-            }
+        public IOffloadHal getOffload() {
+            // Prefer AIDL implementation if its service is declared.
+            IOffloadHal hal = OffloadHalAidlImpl.getIOffloadHal(mHandler, mLog);
             if (hal == null) {
-                try {
-                    hal = IOffloadControl.getService(true /*retry*/);
-                    version = OFFLOAD_HAL_VERSION_1_0;
-                } catch (NoSuchElementException e) {
-                    // Unsupported by device.
-                } catch (RemoteException e) {
-                    mLog.e("Unable to get offload control " + OFFLOAD_HAL_VERSION_1_0);
-                }
+                hal = OffloadHalHidlImpl.getIOffloadHal(mHandler, mLog);
             }
-            return new Pair<IOffloadControl, Integer>(hal, version);
+            return hal;
         }
 
         public NativeHandle createConntrackSocket(final int groups) {
             final FileDescriptor fd;
             try {
-                fd = NetlinkSocket.forProto(OsConstants.NETLINK_NETFILTER);
+                fd = NetlinkUtils.netlinkSocketForProto(OsConstants.NETLINK_NETFILTER);
             } catch (ErrnoException e) {
                 mLog.e("Unable to create conntrack socket " + e);
                 return null;
@@ -273,56 +237,6 @@
         return DEFAULT_TETHER_OFFLOAD_DISABLED;
     }
 
-    /**
-     * Offload management process need to know conntrack rules to support NAT, but it may not have
-     * permission to create netlink netfilter sockets. Create two netlink netfilter sockets and
-     * share them with offload management process.
-     */
-    public boolean initOffloadConfig() {
-        final IOffloadConfig offloadConfig = mDeps.getOffloadConfig();
-        if (offloadConfig == null) {
-            mLog.e("Could not find IOffloadConfig service");
-            return false;
-        }
-        // Per the IConfigOffload definition:
-        //
-        // h1    provides a file descriptor bound to the following netlink groups
-        //       (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY).
-        //
-        // h2    provides a file descriptor bound to the following netlink groups
-        //       (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
-        final NativeHandle h1 = mDeps.createConntrackSocket(
-                NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
-        if (h1 == null) return false;
-
-        requestSocketDump(h1);
-
-        final NativeHandle h2 = mDeps.createConntrackSocket(
-                NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY);
-        if (h2 == null) {
-            closeFdInNativeHandle(h1);
-            return false;
-        }
-
-        final CbResults results = new CbResults();
-        try {
-            offloadConfig.setHandles(h1, h2,
-                    (boolean success, String errMsg) -> {
-                        results.mSuccess = success;
-                        results.mErrMsg = errMsg;
-                    });
-        } catch (RemoteException e) {
-            record("initOffloadConfig, setHandles fail", e);
-            return false;
-        }
-        // Explicitly close FDs.
-        closeFdInNativeHandle(h1);
-        closeFdInNativeHandle(h2);
-
-        record("initOffloadConfig, setHandles results:", results);
-        return results.mSuccess;
-    }
-
     @VisibleForTesting
     void sendIpv4NfGenMsg(@NonNull NativeHandle handle, short type, short flags) {
         final int length = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;
@@ -342,7 +256,7 @@
         nfh.pack(byteBuffer);
 
         try {
-            NetlinkSocket.sendMessage(handle.getFileDescriptor(), msg, 0 /* offset */, length,
+            NetlinkUtils.sendMessage(handle.getFileDescriptor(), msg, 0 /* offset */, length,
                                       NETLINK_MESSAGE_TIMEOUT_MS);
         } catch (ErrnoException | InterruptedIOException e) {
             mLog.e("Unable to send netfilter message, error: " + e);
@@ -355,165 +269,107 @@
                 (short) (NLM_F_REQUEST | NLM_F_DUMP));
     }
 
-    private void closeFdInNativeHandle(final NativeHandle h) {
-        try {
-            h.close();
-        } catch (IOException | IllegalStateException e) {
-            // IllegalStateException means fd is already closed, do nothing here.
-            // Also nothing we can do if IOException.
+    private void maybeCloseFdInNativeHandles(final NativeHandle... handles) {
+        for (NativeHandle h : handles) {
+            if (h == null) continue;
+            try {
+                h.close();
+            } catch (IOException | IllegalStateException e) {
+                // IllegalStateException means fd is already closed, do nothing here.
+                // Also nothing we can do if IOException.
+            }
         }
     }
 
+    private int initWithHandles(NativeHandle h1, NativeHandle h2) {
+        if (h1 == null || h2 == null) {
+            mLog.e("Failed to create socket.");
+            return OFFLOAD_HAL_VERSION_NONE;
+        }
+
+        requestSocketDump(h1);
+        if (!mIOffload.initOffload(h1, h2, mOffloadHalCallback)) {
+            mIOffload.stopOffload();
+            mLog.e("Failed to initialize offload.");
+            return OFFLOAD_HAL_VERSION_NONE;
+        }
+
+        return mIOffload.getVersion();
+    }
+
     /**
      * Initialize the tethering offload HAL.
      *
      * @return one of {@code OFFLOAD_HAL_VERSION_*} represents the HAL version, or
      *         {@link #OFFLOAD_HAL_VERSION_NONE} if failed.
      */
-    public int initOffloadControl(ControlCallback controlCb) {
-        mControlCallback = controlCb;
-
-        if (mOffloadControl == null) {
-            final Pair<IOffloadControl, Integer> halAndVersion = mDeps.getOffloadControl();
-            mOffloadControl = halAndVersion.first;
-            mOffloadControlVersion = halAndVersion.second;
-            if (mOffloadControl == null) {
-                mLog.e("tethering IOffloadControl.getService() returned null");
+    public int initOffload(OffloadHalCallback offloadCb) {
+        if (mIOffload == null) {
+            mIOffload = mDeps.getOffload();
+            if (mIOffload == null) {
+                mLog.i("No tethering offload HAL service found.");
                 return OFFLOAD_HAL_VERSION_NONE;
             }
-            mLog.i("tethering offload control version "
-                    + halVerToString(mOffloadControlVersion) + " is supported.");
+            mLog.i("Tethering offload version "
+                    + halVerToString(mIOffload.getVersion()) + " is supported.");
         }
 
-        final String logmsg = String.format("initOffloadControl(%s)",
-                (controlCb == null) ? "null"
-                        : "0x" + Integer.toHexString(System.identityHashCode(controlCb)));
+        // Per the IOffload definition:
+        //
+        // h1    provides a file descriptor bound to the following netlink groups
+        //       (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY).
+        //
+        // h2    provides a file descriptor bound to the following netlink groups
+        //       (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
+        final NativeHandle h1 = mDeps.createConntrackSocket(
+                NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
+        final NativeHandle h2 = mDeps.createConntrackSocket(
+                NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY);
 
-        mTetheringOffloadCallback = new TetheringOffloadCallback(
-                mHandler, mControlCallback, mLog, mOffloadControlVersion);
-        final CbResults results = new CbResults();
-        try {
-            mOffloadControl.initOffload(
-                    mTetheringOffloadCallback,
-                    (boolean success, String errMsg) -> {
-                        results.mSuccess = success;
-                        results.mErrMsg = errMsg;
-                    });
-        } catch (RemoteException e) {
-            record(logmsg, e);
-            return OFFLOAD_HAL_VERSION_NONE;
+        mOffloadHalCallback = offloadCb;
+        final int version = initWithHandles(h1, h2);
+
+        // Explicitly close FDs for HIDL. AIDL will pass the original FDs to the service,
+        // they shouldn't be closed here.
+        if (version < OFFLOAD_HAL_VERSION_AIDL) {
+            maybeCloseFdInNativeHandles(h1, h2);
         }
-
-        record(logmsg, results);
-        return results.mSuccess ? mOffloadControlVersion : OFFLOAD_HAL_VERSION_NONE;
+        return version;
     }
 
-    /** Stop IOffloadControl. */
-    public void stopOffloadControl() {
-        if (mOffloadControl != null) {
-            try {
-                mOffloadControl.stopOffload(
-                        (boolean success, String errMsg) -> {
-                            if (!success) mLog.e("stopOffload failed: " + errMsg);
-                        });
-            } catch (RemoteException e) {
-                mLog.e("failed to stopOffload: " + e);
+    /** Stop the tethering offload HAL. */
+    public void stopOffload() {
+        if (mIOffload != null) {
+            if (!mIOffload.stopOffload()) {
+                mLog.e("Failed to stop offload.");
             }
         }
-        mOffloadControl = null;
-        mTetheringOffloadCallback = null;
-        mControlCallback = null;
-        mLog.log("stopOffloadControl()");
+        mIOffload = null;
+        mOffloadHalCallback = null;
     }
 
     /** Get Tx/Rx usage from last query. */
     public ForwardedStats getForwardedStats(String upstream) {
-        final String logmsg = String.format("getForwardedStats(%s)",  upstream);
-
-        final ForwardedStats stats = new ForwardedStats();
-        try {
-            mOffloadControl.getForwardedStats(
-                    upstream,
-                    (long rxBytes, long txBytes) -> {
-                        stats.rxBytes = (rxBytes > 0) ? rxBytes : 0;
-                        stats.txBytes = (txBytes > 0) ? txBytes : 0;
-                    });
-        } catch (RemoteException e) {
-            record(logmsg, e);
-            return stats;
-        }
-
-        return stats;
+        return mIOffload.getForwardedStats(upstream);
     }
 
     /** Set local prefixes to offload management process. */
     public boolean setLocalPrefixes(ArrayList<String> localPrefixes) {
-        final String logmsg = String.format("setLocalPrefixes([%s])",
-                String.join(",", localPrefixes));
-
-        final CbResults results = new CbResults();
-        try {
-            mOffloadControl.setLocalPrefixes(localPrefixes,
-                    (boolean success, String errMsg) -> {
-                        results.mSuccess = success;
-                        results.mErrMsg = errMsg;
-                    });
-        } catch (RemoteException e) {
-            record(logmsg, e);
-            return false;
-        }
-
-        record(logmsg, results);
-        return results.mSuccess;
+        return mIOffload.setLocalPrefixes(localPrefixes);
     }
 
     /** Set data limit value to offload management process. */
     public boolean setDataLimit(String iface, long limit) {
-
-        final String logmsg = String.format("setDataLimit(%s, %d)", iface, limit);
-
-        final CbResults results = new CbResults();
-        try {
-            mOffloadControl.setDataLimit(
-                    iface, limit,
-                    (boolean success, String errMsg) -> {
-                        results.mSuccess = success;
-                        results.mErrMsg = errMsg;
-                    });
-        } catch (RemoteException e) {
-            record(logmsg, e);
-            return false;
-        }
-
-        record(logmsg, results);
-        return results.mSuccess;
+        return mIOffload.setDataLimit(iface, limit);
     }
 
     /** Set data warning and limit value to offload management process. */
     public boolean setDataWarningAndLimit(String iface, long warning, long limit) {
-        if (mOffloadControlVersion < OFFLOAD_HAL_VERSION_1_1) {
-            throw new IllegalArgumentException(
+        if (mIOffload.getVersion() < OFFLOAD_HAL_VERSION_HIDL_1_1) {
+            throw new UnsupportedOperationException(
                     "setDataWarningAndLimit is not supported below HAL V1.1");
         }
-        final String logmsg =
-                String.format("setDataWarningAndLimit(%s, %d, %d)", iface, warning, limit);
-
-        final CbResults results = new CbResults();
-        try {
-            ((android.hardware.tetheroffload.control.V1_1.IOffloadControl) mOffloadControl)
-                    .setDataWarningAndLimit(
-                            iface, warning, limit,
-                            (boolean success, String errMsg) -> {
-                                results.mSuccess = success;
-                                results.mErrMsg = errMsg;
-                            });
-        } catch (RemoteException e) {
-            record(logmsg, e);
-            return false;
-        }
-
-        record(logmsg, results);
-        return results.mSuccess;
+        return mIOffload.setDataWarningAndLimit(iface, warning, limit);
     }
 
     /** Set upstream parameters to offload management process. */
@@ -523,178 +379,16 @@
         v4addr = (v4addr != null) ? v4addr : NO_IPV4_ADDRESS;
         v4gateway = (v4gateway != null) ? v4gateway : NO_IPV4_GATEWAY;
         v6gws = (v6gws != null) ? v6gws : new ArrayList<>();
-
-        final String logmsg = String.format("setUpstreamParameters(%s, %s, %s, [%s])",
-                iface, v4addr, v4gateway, String.join(",", v6gws));
-
-        final CbResults results = new CbResults();
-        try {
-            mOffloadControl.setUpstreamParameters(
-                    iface, v4addr, v4gateway, v6gws,
-                    (boolean success, String errMsg) -> {
-                        results.mSuccess = success;
-                        results.mErrMsg = errMsg;
-                    });
-        } catch (RemoteException e) {
-            record(logmsg, e);
-            return false;
-        }
-
-        record(logmsg, results);
-        return results.mSuccess;
+        return mIOffload.setUpstreamParameters(iface, v4addr, v4gateway, v6gws);
     }
 
     /** Add downstream prefix to offload management process. */
-    public boolean addDownstreamPrefix(String ifname, String prefix) {
-        final String logmsg = String.format("addDownstreamPrefix(%s, %s)", ifname, prefix);
-
-        final CbResults results = new CbResults();
-        try {
-            mOffloadControl.addDownstream(ifname, prefix,
-                    (boolean success, String errMsg) -> {
-                        results.mSuccess = success;
-                        results.mErrMsg = errMsg;
-                    });
-        } catch (RemoteException e) {
-            record(logmsg, e);
-            return false;
-        }
-
-        record(logmsg, results);
-        return results.mSuccess;
+    public boolean addDownstream(String ifname, String prefix) {
+        return  mIOffload.addDownstream(ifname, prefix);
     }
 
     /** Remove downstream prefix from offload management process. */
-    public boolean removeDownstreamPrefix(String ifname, String prefix) {
-        final String logmsg = String.format("removeDownstreamPrefix(%s, %s)", ifname, prefix);
-
-        final CbResults results = new CbResults();
-        try {
-            mOffloadControl.removeDownstream(ifname, prefix,
-                    (boolean success, String errMsg) -> {
-                        results.mSuccess = success;
-                        results.mErrMsg = errMsg;
-                    });
-        } catch (RemoteException e) {
-            record(logmsg, e);
-            return false;
-        }
-
-        record(logmsg, results);
-        return results.mSuccess;
-    }
-
-    private void record(String msg, Throwable t) {
-        mLog.e(msg + YIELDS + "exception: " + t);
-    }
-
-    private void record(String msg, CbResults results) {
-        final String logmsg = msg + YIELDS + results;
-        if (!results.mSuccess) {
-            mLog.e(logmsg);
-        } else {
-            mLog.log(logmsg);
-        }
-    }
-
-    private static class TetheringOffloadCallback extends ITetheringOffloadCallback.Stub {
-        public final Handler handler;
-        public final ControlCallback controlCb;
-        public final SharedLog log;
-        private final int mOffloadControlVersion;
-
-        TetheringOffloadCallback(
-                Handler h, ControlCallback cb, SharedLog sharedLog, int offloadControlVersion) {
-            handler = h;
-            controlCb = cb;
-            log = sharedLog;
-            this.mOffloadControlVersion = offloadControlVersion;
-        }
-
-        private void handleOnEvent(int event) {
-            switch (event) {
-                case OffloadCallbackEvent.OFFLOAD_STARTED:
-                    controlCb.onStarted();
-                    break;
-                case OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR:
-                    controlCb.onStoppedError();
-                    break;
-                case OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED:
-                    controlCb.onStoppedUnsupported();
-                    break;
-                case OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE:
-                    controlCb.onSupportAvailable();
-                    break;
-                case OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED:
-                    controlCb.onStoppedLimitReached();
-                    break;
-                case android.hardware.tetheroffload.control
-                        .V1_1.OffloadCallbackEvent.OFFLOAD_WARNING_REACHED:
-                    controlCb.onWarningReached();
-                    break;
-                default:
-                    log.e("Unsupported OffloadCallbackEvent: " + event);
-            }
-        }
-
-        @Override
-        public void onEvent(int event) {
-            // The implementation should never call onEvent()) if the event is already reported
-            // through newer callback.
-            if (mOffloadControlVersion > OFFLOAD_HAL_VERSION_1_0) {
-                Log.wtf(TAG, "onEvent(" + event + ") fired on HAL "
-                        + halVerToString(mOffloadControlVersion));
-            }
-            handler.post(() -> {
-                handleOnEvent(event);
-            });
-        }
-
-        @Override
-        public void onEvent_1_1(int event) {
-            if (mOffloadControlVersion < OFFLOAD_HAL_VERSION_1_1) {
-                Log.wtf(TAG, "onEvent_1_1(" + event + ") fired on HAL "
-                        + halVerToString(mOffloadControlVersion));
-                return;
-            }
-            handler.post(() -> {
-                handleOnEvent(event);
-            });
-        }
-
-        @Override
-        public void updateTimeout(NatTimeoutUpdate params) {
-            handler.post(() -> {
-                controlCb.onNatTimeoutUpdate(
-                        networkProtocolToOsConstant(params.proto),
-                        params.src.addr, uint16(params.src.port),
-                        params.dst.addr, uint16(params.dst.port));
-            });
-        }
-    }
-
-    private static int networkProtocolToOsConstant(int proto) {
-        switch (proto) {
-            case NetworkProtocol.TCP: return OsConstants.IPPROTO_TCP;
-            case NetworkProtocol.UDP: return OsConstants.IPPROTO_UDP;
-            default:
-                // The caller checks this value and will log an error. Just make
-                // sure it won't collide with valid OsContants.IPPROTO_* values.
-                return -Math.abs(proto);
-        }
-    }
-
-    private static class CbResults {
-        boolean mSuccess;
-        String mErrMsg;
-
-        @Override
-        public String toString() {
-            if (mSuccess) {
-                return "ok";
-            } else {
-                return "fail: " + mErrMsg;
-            }
-        }
+    public boolean removeDownstream(String ifname, String prefix) {
+        return  mIOffload.removeDownstream(ifname, prefix);
     }
 }
diff --git a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
index 41a10ae..6c0ca82 100644
--- a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
@@ -16,6 +16,8 @@
 package com.android.networkstack.tethering;
 
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
 import static android.net.TetheringManager.TETHERING_BLUETOOTH;
 import static android.net.TetheringManager.TETHERING_WIFI_P2P;
 
@@ -34,7 +36,6 @@
 import android.net.ip.IpServer;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.SparseArray;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -77,7 +78,7 @@
     private final ConnectivityManager mConnectivityMgr;
     private final TetheringConfiguration mConfig;
     // keyed by downstream type(TetheringManager.TETHERING_*).
-    private final SparseArray<LinkAddress> mCachedAddresses;
+    private final ArrayMap<AddressKey, LinkAddress> mCachedAddresses;
 
     public PrivateAddressCoordinator(Context context, TetheringConfiguration config) {
         mDownstreams = new ArraySet<>();
@@ -85,10 +86,12 @@
         mConnectivityMgr = (ConnectivityManager) context.getSystemService(
                 Context.CONNECTIVITY_SERVICE);
         mConfig = config;
-        mCachedAddresses = new SparseArray<>();
+        mCachedAddresses = new ArrayMap<AddressKey, LinkAddress>();
         // Reserved static addresses for bluetooth and wifi p2p.
-        mCachedAddresses.put(TETHERING_BLUETOOTH, new LinkAddress(LEGACY_BLUETOOTH_IFACE_ADDRESS));
-        mCachedAddresses.put(TETHERING_WIFI_P2P, new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS));
+        mCachedAddresses.put(new AddressKey(TETHERING_BLUETOOTH, CONNECTIVITY_SCOPE_GLOBAL),
+                new LinkAddress(LEGACY_BLUETOOTH_IFACE_ADDRESS));
+        mCachedAddresses.put(new AddressKey(TETHERING_WIFI_P2P, CONNECTIVITY_SCOPE_LOCAL),
+                new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS));
 
         mTetheringPrefixes = new ArrayList<>(Arrays.asList(new IpPrefix("192.168.0.0/16"),
             new IpPrefix("172.16.0.0/12"), new IpPrefix("10.0.0.0/8")));
@@ -166,16 +169,18 @@
      * returns null if there is no available address.
      */
     @Nullable
-    public LinkAddress requestDownstreamAddress(final IpServer ipServer, boolean useLastAddress) {
+    public LinkAddress requestDownstreamAddress(final IpServer ipServer, final int scope,
+            boolean useLastAddress) {
         if (mConfig.shouldEnableWifiP2pDedicatedIp()
                 && ipServer.interfaceType() == TETHERING_WIFI_P2P) {
             return new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS);
         }
 
+        final AddressKey addrKey = new AddressKey(ipServer.interfaceType(), scope);
         // This ensures that tethering isn't started on 2 different interfaces with the same type.
         // Once tethering could support multiple interface with the same type,
         // TetheringSoftApCallback would need to handle it among others.
-        final LinkAddress cachedAddress = mCachedAddresses.get(ipServer.interfaceType());
+        final LinkAddress cachedAddress = mCachedAddresses.get(addrKey);
         if (useLastAddress && cachedAddress != null
                 && !isConflictWithUpstream(asIpPrefix(cachedAddress))) {
             mDownstreams.add(ipServer);
@@ -186,7 +191,7 @@
             final LinkAddress newAddress = chooseDownstreamAddress(prefixRange);
             if (newAddress != null) {
                 mDownstreams.add(ipServer);
-                mCachedAddresses.put(ipServer.interfaceType(), newAddress);
+                mCachedAddresses.put(addrKey, newAddress);
                 return newAddress;
             }
         }
@@ -384,6 +389,34 @@
         return asIpPrefix(address);
     }
 
+    private static class AddressKey {
+        private final int mTetheringType;
+        private final int mScope;
+
+        private AddressKey(int type, int scope) {
+            mTetheringType = type;
+            mScope = scope;
+        }
+
+        @Override
+        public int hashCode() {
+            return (mTetheringType << 16) + mScope;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            if (!(obj instanceof AddressKey)) return false;
+            final AddressKey other = (AddressKey) obj;
+
+            return mTetheringType == other.mTetheringType && mScope == other.mScope;
+        }
+
+        @Override
+        public String toString() {
+            return "AddressKey(" + mTetheringType + ", " + mScope + ")";
+        }
+    }
+
     void dump(final IndentingPrintWriter pw) {
         pw.println("mTetheringPrefixes:");
         pw.increaseIndent();
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index f0dd030..e5f644e 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -58,6 +58,7 @@
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY;
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_UNSPECIFIED;
+import static android.net.wifi.WifiManager.SoftApCallback;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
 import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -88,7 +89,6 @@
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
-import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.TetherStatesParcel;
 import android.net.TetheredClient;
@@ -146,6 +146,7 @@
 import com.android.networkstack.tethering.util.PrefixUtils;
 import com.android.networkstack.tethering.util.TetheringUtils;
 import com.android.networkstack.tethering.util.VersionedBroadcastListener;
+import com.android.networkstack.tethering.wear.WearableConnectionManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -257,6 +258,7 @@
     private final BpfCoordinator mBpfCoordinator;
     private final PrivateAddressCoordinator mPrivateAddressCoordinator;
     private final TetheringMetrics mTetheringMetrics;
+    private final WearableConnectionManager mWearableConnectionManager;
     private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
 
     private volatile TetheringConfiguration mConfig;
@@ -393,6 +395,12 @@
                     }
                 });
 
+        if (SdkLevel.isAtLeastT() && mConfig.isWearTetheringEnabled()) {
+            mWearableConnectionManager = mDeps.getWearableConnectionManager(mContext);
+        } else {
+            mWearableConnectionManager = null;
+        }
+
         startStateMachineUpdaters();
     }
 
@@ -472,15 +480,15 @@
                 mStateReceiver, noUpstreamFilter, PERMISSION_MAINLINE_NETWORK_STACK, mHandler);
 
         final WifiManager wifiManager = getWifiManager();
-        TetheringSoftApCallback softApCallback = new TetheringSoftApCallback();
         if (wifiManager != null) {
-            wifiManager.registerSoftApCallback(mExecutor, softApCallback);
-        }
-        if (SdkLevel.isAtLeastT() && wifiManager != null) {
-            // Although WifiManager#registerLocalOnlyHotspotSoftApCallback document that it need
-            // NEARBY_WIFI_DEVICES permission, but actually a caller who have NETWORK_STACK
-            // or MAINLINE_NETWORK_STACK permission would also able to use this API.
-            wifiManager.registerLocalOnlyHotspotSoftApCallback(mExecutor, softApCallback);
+            wifiManager.registerSoftApCallback(mExecutor, new TetheringSoftApCallback());
+            if (SdkLevel.isAtLeastT()) {
+                // Although WifiManager#registerLocalOnlyHotspotSoftApCallback document that it need
+                // NEARBY_WIFI_DEVICES permission, but actually a caller who have NETWORK_STACK
+                // or MAINLINE_NETWORK_STACK permission can also use this API.
+                wifiManager.registerLocalOnlyHotspotSoftApCallback(mExecutor,
+                        new LocalOnlyHotspotCallback());
+            }
         }
 
         startTrackDefaultNetwork();
@@ -566,26 +574,17 @@
         }
     }
 
-    private class TetheringSoftApCallback implements WifiManager.SoftApCallback {
-        // TODO: Remove onStateChanged override when this method has default on
-        // WifiManager#SoftApCallback interface.
-        // Wifi listener for state change of the soft AP
-        @Override
-        public void onStateChanged(final int state, final int failureReason) {
-            // Nothing
-        }
-
-        // Called by wifi when the number of soft AP clients changed.
-        // Currently multiple softAp would not behave well in PrivateAddressCoordinator
-        // (where it gets the address from cache), it ensure tethering only support one ipServer for
-        // TETHERING_WIFI. Once tethering support multiple softAp enabled simultaneously,
-        // onConnectedClientsChanged should also be updated to support tracking different softAp's
-        // clients individually.
-        // TODO: Add wtf log and have check to reject request duplicated type with different
-        // interface.
+    private class TetheringSoftApCallback implements SoftApCallback {
         @Override
         public void onConnectedClientsChanged(final List<WifiClient> clients) {
-            updateConnectedClients(clients);
+            updateConnectedClients(clients, null);
+        }
+    }
+
+    private class LocalOnlyHotspotCallback implements SoftApCallback {
+        @Override
+        public void onConnectedClientsChanged(final List<WifiClient> clients) {
+            updateConnectedClients(null, clients);
         }
     }
 
@@ -1195,6 +1194,9 @@
         }
 
         private void handleConnectivityAction(Intent intent) {
+            // CONNECTIVITY_ACTION is not handled since U+ device.
+            if (SdkLevel.isAtLeastU()) return;
+
             final NetworkInfo networkInfo =
                     (NetworkInfo) intent.getParcelableExtra(EXTRA_NETWORK_INFO);
             if (networkInfo == null
@@ -1845,9 +1847,13 @@
             final Network newUpstream = (ns != null) ? ns.network : null;
             if (mTetherUpstream != newUpstream) {
                 mTetherUpstream = newUpstream;
-                mUpstreamNetworkMonitor.setCurrentUpstream(mTetherUpstream);
-                reportUpstreamChanged(ns);
+                reportUpstreamChanged(mTetherUpstream);
+                // Need to notify capabilities change after upstream network changed because new
+                // network's capabilities should be checked every time.
+                mNotificationUpdater.onUpstreamCapabilitiesChanged(
+                        (ns != null) ? ns.networkCapabilities : null);
             }
+            mTetheringMetrics.maybeUpdateUpstreamType(ns);
         }
 
         protected void setUpstreamNetwork(UpstreamNetworkState ns) {
@@ -1954,7 +1960,7 @@
             mIPv6TetheringCoordinator.removeActiveDownstream(who);
             mOffload.excludeDownstreamInterface(who.interfaceName());
             mForwardedDownstreams.remove(who);
-            updateConnectedClients(null /* wifiClients */);
+            maybeDhcpLeasesChanged();
 
             // If this is a Wi-Fi interface, tell WifiManager of any errors
             // or the inactive serving state.
@@ -2026,6 +2032,11 @@
                     // broadcasts that result in being passed a
                     // TetherMainSM.CMD_UPSTREAM_CHANGED.
                     handleNewUpstreamNetworkState(null);
+
+                    if (SdkLevel.isAtLeastU()) {
+                        // Need to try DUN immediately if Wi-Fi goes down.
+                        chooseUpstreamType(true);
+                    }
                     break;
                 default:
                     mLog.e("Unknown arg1 value: " + arg1);
@@ -2069,8 +2080,10 @@
                 if (mTetherUpstream != null) {
                     mTetherUpstream = null;
                     reportUpstreamChanged(null);
+                    mNotificationUpdater.onUpstreamCapabilitiesChanged(null);
                 }
                 mBpfCoordinator.stopPolling();
+                mTetheringMetrics.cleanup();
             }
 
             private boolean updateUpstreamWanted() {
@@ -2399,6 +2412,9 @@
 
     /** Unregister tethering event callback */
     void unregisterTetheringEventCallback(ITetheringEventCallback callback) {
+        if (callback == null) {
+            throw new NullPointerException();
+        }
         mHandler.post(() -> {
             mTetheringEventCallbacks.unregister(callback);
         });
@@ -2420,10 +2436,8 @@
         }
     }
 
-    private void reportUpstreamChanged(UpstreamNetworkState ns) {
+    private void reportUpstreamChanged(final Network network) {
         final int length = mTetheringEventCallbacks.beginBroadcast();
-        final Network network = (ns != null) ? ns.network : null;
-        final NetworkCapabilities capabilities = (ns != null) ? ns.networkCapabilities : null;
         try {
             for (int i = 0; i < length; i++) {
                 try {
@@ -2435,9 +2449,6 @@
         } finally {
             mTetheringEventCallbacks.finishBroadcast();
         }
-        // Need to notify capabilities change after upstream network changed because new network's
-        // capabilities should be checked every time.
-        mNotificationUpdater.onUpstreamCapabilitiesChanged(capabilities);
     }
 
     private void reportConfigurationChanged(TetheringConfigurationParcel config) {
@@ -2638,6 +2649,13 @@
         mPrivateAddressCoordinator.dump(pw);
         pw.decreaseIndent();
 
+        if (mWearableConnectionManager != null) {
+            pw.println("WearableConnectionManager:");
+            pw.increaseIndent();
+            mWearableConnectionManager.dump(pw);
+            pw.decreaseIndent();
+        }
+
         pw.println("Log:");
         pw.increaseIndent();
         if (CollectionUtils.contains(args, "--short")) {
@@ -2684,9 +2702,15 @@
         if (e != null) throw e;
     }
 
-    private void updateConnectedClients(final List<WifiClient> wifiClients) {
+    private void maybeDhcpLeasesChanged() {
+        // null means wifi clients did not change.
+        updateConnectedClients(null, null);
+    }
+
+    private void updateConnectedClients(final List<WifiClient> wifiClients,
+            final List<WifiClient> localOnlyClients) {
         if (mConnectedClientsTracker.updateConnectedClients(mTetherMainSM.getAllDownstreams(),
-                wifiClients)) {
+                wifiClients, localOnlyClients)) {
             reportTetherClientsChanged(mConnectedClientsTracker.getLastTetheredClients());
         }
     }
@@ -2705,7 +2729,7 @@
 
             @Override
             public void dhcpLeasesChanged() {
-                updateConnectedClients(null /* wifiClients */);
+                maybeDhcpLeasesChanged();
             }
 
             @Override
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 903de9d..b0aa668 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -39,6 +39,8 @@
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.DeviceConfigUtils;
@@ -123,6 +125,13 @@
      */
     public static final String TETHER_FORCE_USB_FUNCTIONS =
             "tether_force_usb_functions";
+
+    /**
+     * Experiment flag to enable TETHERING_WEAR.
+     */
+    public static final String TETHER_ENABLE_WEAR_TETHERING =
+            "tether_enable_wear_tethering";
+
     /**
      * Default value that used to periodic polls tether offload stats from tethering offload HAL
      * to make the data warnings work.
@@ -151,6 +160,8 @@
 
     public final int activeDataSubId;
 
+    private final Dependencies mDeps;
+
     private final boolean mEnableLegacyDhcpServer;
     private final int mOffloadPollInterval;
     // TODO: Add to TetheringConfigurationParcel if required.
@@ -158,10 +169,36 @@
     private final boolean mEnableWifiP2pDedicatedIp;
     private final int mP2pLeasesSubnetPrefixLength;
 
+    private final boolean mEnableWearTethering;
+
     private final int mUsbTetheringFunction;
     protected final ContentResolver mContentResolver;
 
-    public TetheringConfiguration(Context ctx, SharedLog log, int id) {
+    /**
+     * A class wrapping dependencies of {@link TetheringConfiguration}, useful for testing.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
+                @NonNull String name, @NonNull String moduleName, boolean defaultEnabled) {
+            return DeviceConfigUtils.isFeatureEnabled(context, namespace, name,
+                    moduleName, defaultEnabled);
+        }
+
+        boolean getDeviceConfigBoolean(@NonNull String namespace, @NonNull String name,
+                boolean defaultValue) {
+            return DeviceConfig.getBoolean(namespace, name, defaultValue);
+        }
+    }
+
+    public TetheringConfiguration(@NonNull Context ctx, @NonNull SharedLog log, int id) {
+        this(ctx, log, id, new Dependencies());
+    }
+
+    @VisibleForTesting
+    public TetheringConfiguration(@NonNull Context ctx, @NonNull SharedLog log, int id,
+            @NonNull Dependencies deps) {
+        mDeps = deps;
         final SharedLog configLog = log.forSubComponent("config");
 
         activeDataSubId = id;
@@ -234,6 +271,8 @@
 
         mP2pLeasesSubnetPrefixLength = getP2pLeasesSubnetPrefixLengthFromRes(res, configLog);
 
+        mEnableWearTethering = shouldEnableWearTethering(ctx);
+
         configLog.log(toString());
     }
 
@@ -317,6 +356,11 @@
         return mP2pLeasesSubnetPrefixLength;
     }
 
+    /** Returns true if wearable device tethering is enabled. */
+    public boolean isWearTetheringEnabled() {
+        return mEnableWearTethering;
+    }
+
     /** Does the dumping.*/
     public void dump(PrintWriter pw) {
         pw.print("activeDataSubId: ");
@@ -362,6 +406,9 @@
         pw.print("p2pLeasesSubnetPrefixLength: ");
         pw.println(mP2pLeasesSubnetPrefixLength);
 
+        pw.print("enableWearTethering: ");
+        pw.println(mEnableWearTethering);
+
         pw.print("mUsbTetheringFunction: ");
         pw.println(isUsingNcm() ? "NCM" : "RNDIS");
     }
@@ -387,6 +434,7 @@
                 isCarrierConfigAffirmsEntitlementCheckRequired));
         sj.add(String.format("enableBpfOffload:%s", mEnableBpfOffload));
         sj.add(String.format("enableLegacyDhcpServer:%s", mEnableLegacyDhcpServer));
+        sj.add(String.format("enableWearTethering:%s", mEnableWearTethering));
         return String.format("TetheringConfiguration{%s}", sj.toString());
     }
 
@@ -557,18 +605,13 @@
                 TETHER_ENABLE_LEGACY_DHCP_SERVER, false /** defaultValue */);
     }
 
-    private boolean getDeviceConfigBoolean(final String name, final boolean defaultValue) {
-        // Due to the limitation of static mock for testing, using #getDeviceConfigProperty instead
-        // of DeviceConfig#getBoolean. If using #getBoolean here, the test can't know that the
-        // returned boolean value comes from device config or default value (because of null
-        // property string). See the test case testBpfOffload{*} in TetheringConfigurationTest.java.
-        final String value = getDeviceConfigProperty(name);
-        return value != null ? Boolean.parseBoolean(value) : defaultValue;
+    private boolean shouldEnableWearTethering(Context context) {
+        return SdkLevel.isAtLeastT()
+            && isTetheringFeatureEnabled(context, TETHER_ENABLE_WEAR_TETHERING);
     }
 
-    @VisibleForTesting
-    protected String getDeviceConfigProperty(String name) {
-        return DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, name);
+    private boolean getDeviceConfigBoolean(final String name, final boolean defaultValue) {
+        return mDeps.getDeviceConfigBoolean(NAMESPACE_CONNECTIVITY, name, defaultValue);
     }
 
     /**
@@ -585,10 +628,9 @@
         return isFeatureEnabled(ctx, NAMESPACE_TETHERING, featureVersionFlag);
     }
 
-    @VisibleForTesting
-    protected boolean isFeatureEnabled(Context ctx, String namespace, String featureVersionFlag) {
-        return DeviceConfigUtils.isFeatureEnabled(ctx, namespace, featureVersionFlag,
-                TETHERING_MODULE_NAME, false /* defaultEnabled */);
+    private boolean isFeatureEnabled(Context ctx, String namespace, String featureVersionFlag) {
+        return mDeps.isFeatureEnabled(ctx, namespace, featureVersionFlag, TETHERING_MODULE_NAME,
+                false /* defaultEnabled */);
     }
 
     private Resources getResources(Context ctx, int subId) {
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 611d1cf..741a5c5 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.net.INetd;
 import android.net.ip.IpServer;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -29,12 +30,14 @@
 import android.text.TextUtils;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
 
 import com.android.internal.util.StateMachine;
 import com.android.net.module.util.SharedLog;
 import com.android.networkstack.apishim.BluetoothPanShimImpl;
 import com.android.networkstack.apishim.common.BluetoothPanShim;
 import com.android.networkstack.tethering.metrics.TetheringMetrics;
+import com.android.networkstack.tethering.wear.WearableConnectionManager;
 
 import java.util.ArrayList;
 
@@ -171,4 +174,12 @@
     public TetheringMetrics getTetheringMetrics() {
         return new TetheringMetrics();
     }
+
+    /**
+     * Returns the implementation of WearableConnectionManager.
+     */
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    public WearableConnectionManager getWearableConnectionManager(Context ctx) {
+        return new WearableConnectionManager(ctx);
+    }
 }
diff --git a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
index 16c031b..ac2aa7b 100644
--- a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
+++ b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
@@ -133,8 +133,6 @@
     private boolean mIsDefaultCellularUpstream;
     // The current system default network (not really used yet).
     private Network mDefaultInternetNetwork;
-    // The current upstream network used for tethering.
-    private Network mTetheringUpstreamNetwork;
     private boolean mPreferTestNetworks;
 
     public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, SharedLog log, int what) {
@@ -191,7 +189,6 @@
         releaseCallback(mListenAllCallback);
         mListenAllCallback = null;
 
-        mTetheringUpstreamNetwork = null;
         mNetworkMap.clear();
     }
 
@@ -342,11 +339,6 @@
         return findFirstDunNetwork(mNetworkMap.values());
     }
 
-    /** Tell UpstreamNetworkMonitor which network is the current upstream of tethering. */
-    public void setCurrentUpstream(Network upstream) {
-        mTetheringUpstreamNetwork = upstream;
-    }
-
     /** Return local prefixes. */
     public Set<IpPrefix> getLocalPrefixes() {
         return (Set<IpPrefix>) mLocalPrefixes.clone();
diff --git a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
index ffcea4e..814afcd 100644
--- a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
+++ b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
@@ -16,6 +16,12 @@
 
 package com.android.networkstack.tethering.metrics;
 
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_LOWPAN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
 import static android.net.TetheringManager.TETHERING_BLUETOOTH;
 import static android.net.TetheringManager.TETHERING_ETHERNET;
 import static android.net.TetheringManager.TETHERING_NCM;
@@ -39,6 +45,8 @@
 import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED;
 import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
 
+import android.annotation.Nullable;
+import android.net.NetworkCapabilities;
 import android.stats.connectivity.DownstreamType;
 import android.stats.connectivity.ErrorCode;
 import android.stats.connectivity.UpstreamType;
@@ -49,6 +57,11 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.networkstack.tethering.UpstreamNetworkState;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
 /**
  * Collection of utilities for tethering metrics.
  *
@@ -66,21 +79,58 @@
     private static final String SYSTEMUI_PKG_NAME = "com.android.systemui";
     private static final String GMS_PKG_NAME = "com.google.android.gms";
     private final SparseArray<NetworkTetheringReported.Builder> mBuilderMap = new SparseArray<>();
+    private final SparseArray<Long> mDownstreamStartTime = new SparseArray<Long>();
+    private final ArrayList<RecordUpstreamEvent> mUpstreamEventList = new ArrayList<>();
+    private UpstreamType mCurrentUpstream = null;
+    private Long mCurrentUpStreamStartTime = 0L;
 
-    /** Update Tethering stats about caller's package name and downstream type. */
-    public void createBuilder(final int downstreamType, final String callerPkg) {
-        NetworkTetheringReported.Builder statsBuilder =
-                    NetworkTetheringReported.newBuilder();
-        statsBuilder.setDownstreamType(downstreamTypeToEnum(downstreamType))
-                    .setUserType(userTypeToEnum(callerPkg))
-                    .setUpstreamType(UpstreamType.UT_UNKNOWN)
-                    .setErrorCode(ErrorCode.EC_NO_ERROR)
-                    .setUpstreamEvents(UpstreamEvents.newBuilder())
-                    .setDurationMillis(0);
-        mBuilderMap.put(downstreamType, statsBuilder);
+
+    /**
+     * Return the current system time in milliseconds.
+     * @return the current system time in milliseconds.
+     */
+    public long timeNow() {
+        return System.currentTimeMillis();
     }
 
-    /** Update error code of given downstreamType. */
+    private static class RecordUpstreamEvent {
+        public final long mStartTime;
+        public final long mStopTime;
+        public final UpstreamType mUpstreamType;
+
+        RecordUpstreamEvent(final long startTime, final long stopTime,
+                final UpstreamType upstream) {
+            mStartTime = startTime;
+            mStopTime = stopTime;
+            mUpstreamType = upstream;
+        }
+    }
+
+    /**
+     * Creates a |NetworkTetheringReported.Builder| object to update the tethering stats for the
+     * specified downstream type and caller's package name. Initializes the upstream events, error
+     * code, and duration to default values. Sets the start time for the downstream type in the
+     * |mDownstreamStartTime| map.
+     * @param downstreamType The type of downstream connection (e.g. Wifi, USB, Bluetooth).
+     * @param callerPkg The package name of the caller.
+     */
+    public void createBuilder(final int downstreamType, final String callerPkg) {
+        NetworkTetheringReported.Builder statsBuilder = NetworkTetheringReported.newBuilder()
+                .setDownstreamType(downstreamTypeToEnum(downstreamType))
+                .setUserType(userTypeToEnum(callerPkg))
+                .setUpstreamType(UpstreamType.UT_UNKNOWN)
+                .setErrorCode(ErrorCode.EC_NO_ERROR)
+                .setUpstreamEvents(UpstreamEvents.newBuilder())
+                .setDurationMillis(0);
+        mBuilderMap.put(downstreamType, statsBuilder);
+        mDownstreamStartTime.put(downstreamType, timeNow());
+    }
+
+    /**
+     * Update the error code of the given downstream type in the Tethering stats.
+     * @param downstreamType The downstream type whose error code to update.
+     * @param errCode The error code to set.
+     */
     public void updateErrorCode(final int downstreamType, final int errCode) {
         NetworkTetheringReported.Builder statsBuilder = mBuilderMap.get(downstreamType);
         if (statsBuilder == null) {
@@ -90,38 +140,150 @@
         statsBuilder.setErrorCode(errorCodeToEnum(errCode));
     }
 
-    /** Remove Tethering stats.
-     *  If Tethering stats is ready to write then write it before removing.
+    /**
+     * Update the list of upstream types and their duration whenever the current upstream type
+     * changes.
+     * @param ns The UpstreamNetworkState object representing the current upstream network state.
+     */
+    public void maybeUpdateUpstreamType(@Nullable final UpstreamNetworkState ns) {
+        UpstreamType upstream = transportTypeToUpstreamTypeEnum(ns);
+        if (upstream.equals(mCurrentUpstream)) return;
+
+        final long newTime = timeNow();
+        if (mCurrentUpstream != null) {
+            mUpstreamEventList.add(new RecordUpstreamEvent(mCurrentUpStreamStartTime, newTime,
+                    mCurrentUpstream));
+        }
+        mCurrentUpstream = upstream;
+        mCurrentUpStreamStartTime = newTime;
+    }
+
+    /**
+     * Updates the upstream events builder with a new upstream event.
+     * @param upstreamEventsBuilder the builder for the upstream events list
+     * @param start the start time of the upstream event
+     * @param stop the stop time of the upstream event
+     * @param upstream the type of upstream type (e.g. Wifi, Cellular, Bluetooth, ...)
+     */
+    private void addUpstreamEvent(final UpstreamEvents.Builder upstreamEventsBuilder,
+            final long start, final long stop, @Nullable final UpstreamType upstream,
+            final long txBytes, final long rxBytes) {
+        final UpstreamEvent.Builder upstreamEventBuilder = UpstreamEvent.newBuilder()
+                .setUpstreamType(upstream == null ? UpstreamType.UT_NO_NETWORK : upstream)
+                .setDurationMillis(stop - start)
+                .setTxBytes(txBytes)
+                .setRxBytes(rxBytes);
+        upstreamEventsBuilder.addUpstreamEvent(upstreamEventBuilder);
+    }
+
+    /**
+     * Updates the |NetworkTetheringReported.Builder| with relevant upstream events associated with
+     * the downstream event identified by the given downstream start time.
+     *
+     * This method iterates through the list of upstream events and adds any relevant events to a
+     * |UpstreamEvents.Builder|. Upstream events are considered relevant if their stop time is
+     * greater than or equal to the given downstream start time. The method also adds the last
+     * upstream event that occurred up until the current time.
+     *
+     * The resulting |UpstreamEvents.Builder| is then added to the
+     * |NetworkTetheringReported.Builder|, along with the duration of the downstream event
+     * (i.e., stop time minus downstream start time).
+     *
+     * @param statsBuilder the builder for the NetworkTetheringReported message
+     * @param downstreamStartTime the start time of the downstream event to find relevant upstream
+     * events for
+     */
+    private void noteDownstreamStopped(final NetworkTetheringReported.Builder statsBuilder,
+                    final long downstreamStartTime) {
+        UpstreamEvents.Builder upstreamEventsBuilder = UpstreamEvents.newBuilder();
+
+        for (RecordUpstreamEvent event : mUpstreamEventList) {
+            if (downstreamStartTime > event.mStopTime) continue;
+
+            final long startTime = Math.max(downstreamStartTime, event.mStartTime);
+            // Handle completed upstream events.
+            addUpstreamEvent(upstreamEventsBuilder, startTime, event.mStopTime,
+                    event.mUpstreamType, 0L /* txBytes */, 0L /* rxBytes */);
+        }
+        final long startTime = Math.max(downstreamStartTime, mCurrentUpStreamStartTime);
+        final long stopTime = timeNow();
+        // Handle the last upstream event.
+        addUpstreamEvent(upstreamEventsBuilder, startTime, stopTime, mCurrentUpstream,
+                0L /* txBytes */, 0L /* rxBytes */);
+        statsBuilder.setUpstreamEvents(upstreamEventsBuilder);
+        statsBuilder.setDurationMillis(stopTime - downstreamStartTime);
+    }
+
+    /**
+     * Removes tethering statistics for the given downstream type. If there are any stats to write
+     * for the downstream event associated with the type, they are written before removing the
+     * statistics.
+     *
+     * If the given downstream type does not exist in the map, an error message is logged and the
+     * method returns without doing anything.
+     *
+     * @param downstreamType the type of downstream event to remove statistics for
      */
     public void sendReport(final int downstreamType) {
-        final NetworkTetheringReported.Builder statsBuilder =
-                mBuilderMap.get(downstreamType);
+        final NetworkTetheringReported.Builder statsBuilder = mBuilderMap.get(downstreamType);
         if (statsBuilder == null) {
             Log.e(TAG, "Given downstreamType does not exist, this is a bug!");
             return;
         }
+
+        noteDownstreamStopped(statsBuilder, mDownstreamStartTime.get(downstreamType));
         write(statsBuilder.build());
+
         mBuilderMap.remove(downstreamType);
+        mDownstreamStartTime.remove(downstreamType);
     }
 
-    /** Collect Tethering stats and write metrics data to statsd pipeline. */
+    /**
+     * Collects tethering statistics and writes them to the statsd pipeline. This method takes in a
+     * NetworkTetheringReported object, extracts its fields and uses them to write statistics data
+     * to the statsd pipeline.
+     *
+     * @param reported a NetworkTetheringReported object containing statistics to write
+     */
     @VisibleForTesting
     public void write(@NonNull final NetworkTetheringReported reported) {
-        TetheringStatsLog.write(TetheringStatsLog.NETWORK_TETHERING_REPORTED,
+        final byte[] upstreamEvents = reported.getUpstreamEvents().toByteArray();
+
+        TetheringStatsLog.write(
+                TetheringStatsLog.NETWORK_TETHERING_REPORTED,
                 reported.getErrorCode().getNumber(),
                 reported.getDownstreamType().getNumber(),
                 reported.getUpstreamType().getNumber(),
                 reported.getUserType().getNumber(),
-                null, 0);
+                upstreamEvents,
+                reported.getDurationMillis());
         if (DBG) {
-            Log.d(TAG, "Write errorCode: " + reported.getErrorCode().getNumber()
-                    + ", downstreamType: " + reported.getDownstreamType().getNumber()
-                    + ", upstreamType: " + reported.getUpstreamType().getNumber()
-                    + ", userType: " + reported.getUserType().getNumber());
+            Log.d(
+                    TAG,
+                    "Write errorCode: "
+                    + reported.getErrorCode().getNumber()
+                    + ", downstreamType: "
+                    + reported.getDownstreamType().getNumber()
+                    + ", upstreamType: "
+                    + reported.getUpstreamType().getNumber()
+                    + ", userType: "
+                    + reported.getUserType().getNumber()
+                    + ", upstreamTypes: "
+                    + Arrays.toString(upstreamEvents)
+                    + ", durationMillis: "
+                    + reported.getDurationMillis());
         }
     }
 
-    /** Map {@link TetheringType} to {@link DownstreamType} */
+    /**
+     * Cleans up the variables related to upstream events when tethering is turned off.
+     */
+    public void cleanup() {
+        mUpstreamEventList.clear();
+        mCurrentUpstream = null;
+        mCurrentUpStreamStartTime = 0L;
+    }
+
     private DownstreamType downstreamTypeToEnum(final int ifaceType) {
         switch(ifaceType) {
             case TETHERING_WIFI:
@@ -141,7 +303,6 @@
         }
     }
 
-    /** Map {@link StartTetheringError} to {@link ErrorCode} */
     private ErrorCode errorCodeToEnum(final int lastError) {
         switch(lastError) {
             case TETHER_ERROR_NO_ERROR:
@@ -181,7 +342,6 @@
         }
     }
 
-    /** Map callerPkg to {@link UserType} */
     private UserType userTypeToEnum(final String callerPkg) {
         if (callerPkg.equals(SETTINGS_PKG_NAME)) {
             return UserType.USER_SETTINGS;
@@ -193,4 +353,25 @@
             return UserType.USER_UNKNOWN;
         }
     }
+
+    private UpstreamType transportTypeToUpstreamTypeEnum(final UpstreamNetworkState ns) {
+        final NetworkCapabilities nc = (ns != null) ? ns.networkCapabilities : null;
+        if (nc == null) return UpstreamType.UT_NO_NETWORK;
+
+        final int typeCount = nc.getTransportTypes().length;
+        // It's possible for a VCN network to be mapped to UT_UNKNOWN, as it may consist of both
+        // Wi-Fi and cellular transport.
+        // TODO: It's necessary to define a new upstream type for VCN, which can be identified by
+        // NET_CAPABILITY_NOT_VCN_MANAGED.
+        if (typeCount > 1) return UpstreamType.UT_UNKNOWN;
+
+        if (nc.hasTransport(TRANSPORT_CELLULAR)) return UpstreamType.UT_CELLULAR;
+        if (nc.hasTransport(TRANSPORT_WIFI)) return UpstreamType.UT_WIFI;
+        if (nc.hasTransport(TRANSPORT_BLUETOOTH)) return UpstreamType.UT_BLUETOOTH;
+        if (nc.hasTransport(TRANSPORT_ETHERNET)) return UpstreamType.UT_ETHERNET;
+        if (nc.hasTransport(TRANSPORT_WIFI_AWARE)) return UpstreamType.UT_WIFI_AWARE;
+        if (nc.hasTransport(TRANSPORT_LOWPAN)) return UpstreamType.UT_LOWPAN;
+
+        return UpstreamType.UT_UNKNOWN;
+    }
 }
diff --git a/Tethering/src/com/android/networkstack/tethering/metrics/stats.proto b/Tethering/src/com/android/networkstack/tethering/metrics/stats.proto
index 27f2126..b276389 100644
--- a/Tethering/src/com/android/networkstack/tethering/metrics/stats.proto
+++ b/Tethering/src/com/android/networkstack/tethering/metrics/stats.proto
@@ -21,13 +21,21 @@
 
 import "frameworks/proto_logging/stats/enums/stats/connectivity/tethering.proto";
 
-// Logs each upstream for a successful switch over
+/**
+ * Represents an event that logs information about a successful switch to an upstream network.
+ */
 message UpstreamEvent {
-  // Transport type of upstream network
+  // Indicates the transport type of network.
   optional .android.stats.connectivity.UpstreamType upstream_type = 1;
 
-  // A time period that an upstream continued
+  // The duration of network usage.
   optional int64 duration_millis = 2;
+
+  // The amount of data received from tethered clients.
+  optional int64 tx_bytes = 3;
+
+  // The amount of data received from remote.
+  optional int64 rx_bytes = 4;
 }
 
 message UpstreamEvents {
diff --git a/Tethering/src/com/android/networkstack/tethering/wear/CompanionDeviceManagerProxy.java b/Tethering/src/com/android/networkstack/tethering/wear/CompanionDeviceManagerProxy.java
new file mode 100644
index 0000000..e94febb
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/wear/CompanionDeviceManagerProxy.java
@@ -0,0 +1,55 @@
+/*
+ * 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 com.android.networkstack.tethering.wear;
+
+import android.companion.AssociationInfo;
+import android.companion.CompanionDeviceManager;
+import android.content.Context;
+import android.net.connectivity.TiramisuConnectivityInternalApiUtil;
+import android.net.wear.ICompanionDeviceManagerProxy;
+import android.os.Build;
+import android.os.RemoteException;
+
+import androidx.annotation.RequiresApi;
+
+import java.util.List;
+
+/**
+ * A proxy for {@link android.companion.CompanionDeviceManager}, allowing Tethering to call it with
+ * a different set of permissions.
+ * @hide
+ */
+public class CompanionDeviceManagerProxy {
+    private final ICompanionDeviceManagerProxy mService;
+
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    public CompanionDeviceManagerProxy(Context context) {
+        mService = ICompanionDeviceManagerProxy.Stub.asInterface(
+                TiramisuConnectivityInternalApiUtil.getCompanionDeviceManagerProxyService(context));
+    }
+
+    /**
+     * @see CompanionDeviceManager#getAllAssociations()
+     */
+    public List<AssociationInfo> getAllAssociations() {
+        try {
+            return mService.getAllAssociations();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/wear/WearableConnectionManager.java b/Tethering/src/com/android/networkstack/tethering/wear/WearableConnectionManager.java
new file mode 100644
index 0000000..a1b535a
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/wear/WearableConnectionManager.java
@@ -0,0 +1,36 @@
+/*
+ * 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 com.android.networkstack.tethering.wear;
+
+import android.content.Context;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+/**
+ * Manages Bluetooth connections from Wearable devices.
+ */
+public class WearableConnectionManager {
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    public WearableConnectionManager(Context context) {
+    }
+
+    public void dump(IndentingPrintWriter pw) {
+    }
+}
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index 11e3dc0..5e08aba 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -21,8 +21,7 @@
     name: "TetheringIntegrationTestsDefaults",
     defaults: ["framework-connectivity-test-defaults"],
     srcs: [
-        "src/**/*.java",
-        "src/**/*.kt",
+        "base/**/*.java",
     ],
     min_sdk_version: "30",
     static_libs: [
@@ -47,6 +46,16 @@
     ],
 }
 
+android_library {
+    name: "TetheringIntegrationTestsBaseLib",
+    target_sdk_version: "current",
+    platform_apis: true,
+    defaults: ["TetheringIntegrationTestsDefaults"],
+    visibility: [
+        "//packages/modules/Connectivity/Tethering/tests/mts",
+    ]
+}
+
 // Library including tethering integration tests targeting the latest stable SDK.
 // Use with NetworkStackJarJarRules.
 android_library {
@@ -54,6 +63,9 @@
     target_sdk_version: "33",
     platform_apis: true,
     defaults: ["TetheringIntegrationTestsDefaults"],
+    srcs: [
+        "src/**/*.java",
+    ],
     visibility: [
         "//packages/modules/Connectivity/tests/cts/tethering",
         "//packages/modules/Connectivity/tests:__subpackages__",
@@ -68,12 +80,16 @@
     target_sdk_version: "current",
     platform_apis: true,
     defaults: ["TetheringIntegrationTestsDefaults"],
+    srcs: [
+        "src/**/*.java",
+    ],
     visibility: [
         "//packages/modules/Connectivity/tests/cts/tethering",
         "//packages/modules/Connectivity/Tethering/tests/mts",
     ]
 }
 
+// TODO: remove because TetheringIntegrationTests has been covered by ConnectivityCoverageTests.
 android_test {
     name: "TetheringIntegrationTests",
     platform_apis: true,
@@ -81,6 +97,9 @@
     test_suites: [
         "device-tests",
     ],
+    srcs: [
+        "src/**/*.java",
+    ],
     compile_multilib: "both",
     jarjar_rules: ":NetworkStackJarJarRules",
 }
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
similarity index 97%
rename from Tethering/tests/integration/src/android/net/EthernetTetheringTestBase.java
rename to Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
index 7685981..007bf23 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
@@ -72,6 +72,8 @@
 
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.PacketBuilder;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.Ipv6Header;
 import com.android.testutils.HandlerUtils;
 import com.android.testutils.TapPacketReader;
 import com.android.testutils.TestNetworkTracker;
@@ -213,6 +215,13 @@
         }
     }
 
+    protected void stopEthernetTethering(final MyTetheringEventCallback callback) {
+        runAsShell(TETHER_PRIVILEGED, () -> {
+            mTm.stopTethering(TETHERING_ETHERNET);
+            maybeUnregisterTetheringEventCallback(callback);
+        });
+    }
+
     protected void cleanUp() throws Exception {
         setPreferTestNetworks(false);
 
@@ -251,6 +260,7 @@
             if (mRunTests) cleanUp();
         } finally {
             mHandlerThread.quitSafely();
+            mHandlerThread.join();
             mUiAutomation.dropShellPermissionIdentity();
         }
     }
@@ -1013,6 +1023,18 @@
         return new TetheringTester(mDownstreamReader, mUpstreamReader);
     }
 
+    @NonNull
+    protected Inet6Address getClatIpv6Address(TetheringTester tester, TetheredDevice tethered)
+            throws Exception {
+        // Send an IPv4 UDP packet from client and check that a CLAT translated IPv6 UDP packet can
+        // be found on upstream interface. Get CLAT IPv6 address from the CLAT translated IPv6 UDP
+        // packet.
+        byte[] expectedPacket = probeV4TetheringConnectivity(tester, tethered, true /* is4To6 */);
+
+        // Above has guaranteed that the found packet is an IPv6 packet without ether header.
+        return Struct.parse(Ipv6Header.class, ByteBuffer.wrap(expectedPacket)).srcIp;
+    }
+
     protected <T> List<T> toList(T... array) {
         return Arrays.asList(array);
     }
diff --git a/Tethering/tests/integration/src/android/net/TetheringTester.java b/Tethering/tests/integration/base/android/net/TetheringTester.java
similarity index 98%
rename from Tethering/tests/integration/src/android/net/TetheringTester.java
rename to Tethering/tests/integration/base/android/net/TetheringTester.java
index ae39b24..1c0803e 100644
--- a/Tethering/tests/integration/src/android/net/TetheringTester.java
+++ b/Tethering/tests/integration/base/android/net/TetheringTester.java
@@ -628,7 +628,7 @@
         return false;
     }
 
-    private void sendUploadPacket(ByteBuffer packet) throws Exception {
+    public void sendUploadPacket(ByteBuffer packet) throws Exception {
         mDownstreamReader.sendResponse(packet);
     }
 
@@ -680,4 +680,12 @@
 
         return verifyPacketNotNull("Download fail", getDownloadPacket(filter));
     }
+
+    // Send DHCPDISCOVER to DHCP server to see if DHCP server is still alive to handle
+    // the upcoming DHCP packets. This method should be only used when we know the DHCP
+    // server has been created successfully before.
+    public boolean testDhcpServerAlive(final MacAddress mac) throws Exception {
+        sendDhcpDiscover(mac.toByteArray());
+        return getNextDhcpPacket() != null;
+    }
 }
diff --git a/Tethering/tests/integration/src/android/net/CtsEthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
similarity index 77%
rename from Tethering/tests/integration/src/android/net/CtsEthernetTetheringTest.java
rename to Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index aea6728..4ee5c42 100644
--- a/Tethering/tests/integration/src/android/net/CtsEthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -16,7 +16,6 @@
 
 package android.net;
 
-import static android.Manifest.permission.DUMP;
 import static android.net.InetAddresses.parseNumericAddress;
 import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
 import static android.net.TetheringManager.TETHERING_ETHERNET;
@@ -26,7 +25,6 @@
 import static android.system.OsConstants.ICMP_ECHO;
 import static android.system.OsConstants.ICMP_ECHOREPLY;
 import static android.system.OsConstants.IPPROTO_ICMP;
-import static android.system.OsConstants.IPPROTO_UDP;
 
 import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA;
 import static com.android.net.module.util.HexDump.dumpHexString;
@@ -39,48 +37,34 @@
 import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET;
 import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
 import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET;
-import static com.android.testutils.DeviceInfoUtils.KVersion;
-import static com.android.testutils.TestPermissionUtil.runAsShell;
 
 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 static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
-import android.content.Context;
 import android.net.TetheringManager.TetheringRequest;
 import android.net.TetheringTester.TetheredDevice;
 import android.os.Build;
 import android.os.SystemClock;
 import android.os.SystemProperties;
-import android.os.VintfRuntimeInfo;
 import android.util.Log;
-import android.util.Pair;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.net.module.util.BpfDump;
 import com.android.net.module.util.Ipv6Utils;
 import com.android.net.module.util.Struct;
-import com.android.net.module.util.bpf.Tether4Key;
-import com.android.net.module.util.bpf.Tether4Value;
-import com.android.net.module.util.bpf.TetherStatsKey;
-import com.android.net.module.util.bpf.TetherStatsValue;
 import com.android.net.module.util.structs.EthernetHeader;
 import com.android.net.module.util.structs.Icmpv4Header;
 import com.android.net.module.util.structs.Ipv4Header;
-import com.android.net.module.util.structs.Ipv6Header;
 import com.android.net.module.util.structs.UdpHeader;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
-import com.android.testutils.DeviceInfoUtils;
-import com.android.testutils.DumpTestUtils;
 import com.android.testutils.TapPacketReader;
 
 import org.junit.Rule;
@@ -96,9 +80,7 @@
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Random;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
@@ -106,34 +88,13 @@
 
 @RunWith(AndroidJUnit4.class)
 @MediumTest
-public class CtsEthernetTetheringTest extends EthernetTetheringTestBase {
+public class EthernetTetheringTest extends EthernetTetheringTestBase {
     @Rule
     public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
 
-    private static final String TAG = CtsEthernetTetheringTest.class.getSimpleName();
-
-    private static final int DUMP_POLLING_MAX_RETRY = 100;
-    private static final int DUMP_POLLING_INTERVAL_MS = 50;
-    // Kernel treats a confirmed UDP connection which active after two seconds as stream mode.
-    // See upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5.
-    private static final int UDP_STREAM_TS_MS = 2000;
-    // Give slack time for waiting UDP stream mode because handling conntrack event in user space
-    // may not in precise time. Used to reduce the flaky rate.
-    private static final int UDP_STREAM_SLACK_MS = 500;
-    // Per RX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
-    private static final int RX_UDP_PACKET_SIZE = 30;
-    private static final int RX_UDP_PACKET_COUNT = 456;
-    // Per TX UDP packet size: ethhdr (14) + iphdr (20) + udphdr (8) + payload (2) = 44 bytes.
-    private static final int TX_UDP_PACKET_SIZE = 44;
-    private static final int TX_UDP_PACKET_COUNT = 123;
+    private static final String TAG = EthernetTetheringTest.class.getSimpleName();
 
     private static final short DNS_PORT = 53;
-
-    private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
-    private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
-    private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
-    private static final String LINE_DELIMITER = "\\n";
-
     private static final short ICMPECHO_CODE = 0x0;
     private static final short ICMPECHO_ID = 0x0;
     private static final short ICMPECHO_SEQ = 0x0;
@@ -403,7 +364,7 @@
             // Enable Ethernet tethering and check that it starts.
             tetheringEventCallback = enableEthernetTethering(iface, null /* any upstream */);
         } finally {
-            maybeUnregisterTetheringEventCallback(tetheringEventCallback);
+            stopEthernetTethering(tetheringEventCallback);
         }
         // There is nothing more we can do on a physical interface without connecting an actual
         // client, which is not possible in this test.
@@ -529,7 +490,7 @@
     // remote ip              public ip                           private ip
     // 8.8.8.8:443            <Upstream ip>:9876                  <TetheredDevice ip>:9876
     //
-    private void runUdp4Test(boolean verifyBpf) throws Exception {
+    private void runUdp4Test() throws Exception {
         final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
                 toList(TEST_IP4_DNS));
         final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
@@ -549,123 +510,6 @@
         final InetAddress clientIp = tethered.ipv4Addr;
         sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
         sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
-
-        if (verifyBpf) {
-            // Send second UDP packet in original direction.
-            // The BPF coordinator only offloads the ASSURED conntrack entry. The "request + reply"
-            // packets can make status IPS_SEEN_REPLY to be set. Need one more packet to make
-            // conntrack status IPS_ASSURED_BIT to be set. Note the third packet needs to delay
-            // 2 seconds because kernel monitors a UDP connection which still alive after 2 seconds
-            // and apply ASSURED flag.
-            // See kernel upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5 and
-            // nf_conntrack_udp_packet in net/netfilter/nf_conntrack_proto_udp.c
-            Thread.sleep(UDP_STREAM_TS_MS);
-            sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
-
-            // Give a slack time for handling conntrack event in user space.
-            Thread.sleep(UDP_STREAM_SLACK_MS);
-
-            // [1] Verify IPv4 upstream rule map.
-            final HashMap<Tether4Key, Tether4Value> upstreamMap = pollRawMapFromDump(
-                    Tether4Key.class, Tether4Value.class, DUMPSYS_RAWMAP_ARG_UPSTREAM4);
-            assertNotNull(upstreamMap);
-            assertEquals(1, upstreamMap.size());
-
-            final Map.Entry<Tether4Key, Tether4Value> rule =
-                    upstreamMap.entrySet().iterator().next();
-
-            final Tether4Key upstream4Key = rule.getKey();
-            assertEquals(IPPROTO_UDP, upstream4Key.l4proto);
-            assertTrue(Arrays.equals(tethered.ipv4Addr.getAddress(), upstream4Key.src4));
-            assertEquals(LOCAL_PORT, upstream4Key.srcPort);
-            assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(), upstream4Key.dst4));
-            assertEquals(REMOTE_PORT, upstream4Key.dstPort);
-
-            final Tether4Value upstream4Value = rule.getValue();
-            assertTrue(Arrays.equals(tetheringUpstreamIp.getAddress(),
-                    InetAddress.getByAddress(upstream4Value.src46).getAddress()));
-            assertEquals(LOCAL_PORT, upstream4Value.srcPort);
-            assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(),
-                    InetAddress.getByAddress(upstream4Value.dst46).getAddress()));
-            assertEquals(REMOTE_PORT, upstream4Value.dstPort);
-
-            // [2] Verify stats map.
-            // Transmit packets on both direction for verifying stats. Because we only care the
-            // packet count in stats test, we just reuse the existing packets to increaes
-            // the packet count on both direction.
-
-            // Send packets on original direction.
-            for (int i = 0; i < TX_UDP_PACKET_COUNT; i++) {
-                sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester,
-                        false /* is4To6 */);
-            }
-
-            // Send packets on reply direction.
-            for (int i = 0; i < RX_UDP_PACKET_COUNT; i++) {
-                sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
-            }
-
-            // Dump stats map to verify.
-            final HashMap<TetherStatsKey, TetherStatsValue> statsMap = pollRawMapFromDump(
-                    TetherStatsKey.class, TetherStatsValue.class, DUMPSYS_RAWMAP_ARG_STATS);
-            assertNotNull(statsMap);
-            assertEquals(1, statsMap.size());
-
-            final Map.Entry<TetherStatsKey, TetherStatsValue> stats =
-                    statsMap.entrySet().iterator().next();
-
-            // TODO: verify the upstream index in TetherStatsKey.
-
-            final TetherStatsValue statsValue = stats.getValue();
-            assertEquals(RX_UDP_PACKET_COUNT, statsValue.rxPackets);
-            assertEquals(RX_UDP_PACKET_COUNT * RX_UDP_PACKET_SIZE, statsValue.rxBytes);
-            assertEquals(0, statsValue.rxErrors);
-            assertEquals(TX_UDP_PACKET_COUNT, statsValue.txPackets);
-            assertEquals(TX_UDP_PACKET_COUNT * TX_UDP_PACKET_SIZE, statsValue.txBytes);
-            assertEquals(0, statsValue.txErrors);
-        }
-    }
-
-    private static boolean isUdpOffloadSupportedByKernel(final String kernelVersion) {
-        final KVersion current = DeviceInfoUtils.getMajorMinorSubminorVersion(kernelVersion);
-        return current.isInRange(new KVersion(4, 14, 222), new KVersion(4, 19, 0))
-                || current.isInRange(new KVersion(4, 19, 176), new KVersion(5, 4, 0))
-                || current.isAtLeast(new KVersion(5, 4, 98));
-    }
-
-    @Test
-    public void testIsUdpOffloadSupportedByKernel() throws Exception {
-        assertFalse(isUdpOffloadSupportedByKernel("4.14.221"));
-        assertTrue(isUdpOffloadSupportedByKernel("4.14.222"));
-        assertTrue(isUdpOffloadSupportedByKernel("4.16.0"));
-        assertTrue(isUdpOffloadSupportedByKernel("4.18.0"));
-        assertFalse(isUdpOffloadSupportedByKernel("4.19.0"));
-
-        assertFalse(isUdpOffloadSupportedByKernel("4.19.175"));
-        assertTrue(isUdpOffloadSupportedByKernel("4.19.176"));
-        assertTrue(isUdpOffloadSupportedByKernel("5.2.0"));
-        assertTrue(isUdpOffloadSupportedByKernel("5.3.0"));
-        assertFalse(isUdpOffloadSupportedByKernel("5.4.0"));
-
-        assertFalse(isUdpOffloadSupportedByKernel("5.4.97"));
-        assertTrue(isUdpOffloadSupportedByKernel("5.4.98"));
-        assertTrue(isUdpOffloadSupportedByKernel("5.10.0"));
-    }
-
-    private static void assumeKernelSupportBpfOffloadUdpV4() {
-        final String kernelVersion = VintfRuntimeInfo.getKernelVersion();
-        assumeTrue("Kernel version " + kernelVersion + " doesn't support IPv4 UDP BPF offload",
-                isUdpOffloadSupportedByKernel(kernelVersion));
-    }
-
-    @Test
-    public void testKernelSupportBpfOffloadUdpV4() throws Exception {
-        assumeKernelSupportBpfOffloadUdpV4();
-    }
-
-    @Test
-    public void testTetherConfigBpfOffloadEnabled() throws Exception {
-        assumeTrue(isTetherConfigBpfOffloadEnabled());
     }
 
     /**
@@ -674,85 +518,7 @@
      */
     @Test
     public void testTetherUdpV4() throws Exception {
-        runUdp4Test(false /* verifyBpf */);
-    }
-
-    /**
-     * BPF offload IPv4 UDP tethering test. Verify that UDP tethered packets are offloaded by BPF.
-     * Minimum test requirement:
-     * 1. S+ device.
-     * 2. Tethering config enables tethering BPF offload.
-     * 3. Kernel supports IPv4 UDP BPF offload. See #isUdpOffloadSupportedByKernel.
-     *
-     * TODO: consider enabling the test even tethering config disables BPF offload. See b/238288883
-     */
-    @Test
-    @IgnoreUpTo(Build.VERSION_CODES.R)
-    public void testTetherUdpV4_VerifyBpf() throws Exception {
-        assumeTrue("Tethering config disabled BPF offload", isTetherConfigBpfOffloadEnabled());
-        assumeKernelSupportBpfOffloadUdpV4();
-
-        runUdp4Test(true /* verifyBpf */);
-    }
-
-    @NonNull
-    private <K extends Struct, V extends Struct> HashMap<K, V> dumpAndParseRawMap(
-            Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
-            throws Exception {
-        final String[] args = new String[] {DUMPSYS_TETHERING_RAWMAP_ARG, mapArg};
-        final String rawMapStr = runAsShell(DUMP, () ->
-                DumpTestUtils.dumpService(Context.TETHERING_SERVICE, args));
-        final HashMap<K, V> map = new HashMap<>();
-
-        for (final String line : rawMapStr.split(LINE_DELIMITER)) {
-            final Pair<K, V> rule =
-                    BpfDump.fromBase64EncodedString(keyClass, valueClass, line.trim());
-            map.put(rule.first, rule.second);
-        }
-        return map;
-    }
-
-    @Nullable
-    private <K extends Struct, V extends Struct> HashMap<K, V> pollRawMapFromDump(
-            Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
-            throws Exception {
-        for (int retryCount = 0; retryCount < DUMP_POLLING_MAX_RETRY; retryCount++) {
-            final HashMap<K, V> map = dumpAndParseRawMap(keyClass, valueClass, mapArg);
-            if (!map.isEmpty()) return map;
-
-            Thread.sleep(DUMP_POLLING_INTERVAL_MS);
-        }
-
-        fail("Cannot get rules after " + DUMP_POLLING_MAX_RETRY * DUMP_POLLING_INTERVAL_MS + "ms");
-        return null;
-    }
-
-    private boolean isTetherConfigBpfOffloadEnabled() throws Exception {
-        final String dumpStr = runAsShell(DUMP, () ->
-                DumpTestUtils.dumpService(Context.TETHERING_SERVICE, "--short"));
-
-        // BPF offload tether config can be overridden by "config_tether_enable_bpf_offload" in
-        // packages/modules/Connectivity/Tethering/res/values/config.xml. OEM may disable config by
-        // RRO to override the enabled default value. Get the tethering config via dumpsys.
-        // $ dumpsys tethering
-        //   mIsBpfEnabled: true
-        boolean enabled = dumpStr.contains("mIsBpfEnabled: true");
-        if (!enabled) {
-            Log.d(TAG, "BPF offload tether config not enabled: " + dumpStr);
-        }
-        return enabled;
-    }
-
-    @NonNull
-    private Inet6Address getClatIpv6Address(TetheringTester tester, TetheredDevice tethered)
-            throws Exception {
-        // Send an IPv4 UDP packet from client and check that a CLAT translated IPv6 UDP packet can
-        // be found on upstream interface. Get CLAT IPv6 address from the CLAT translated IPv6 UDP
-        // packet.
-        byte[] expectedPacket = probeV4TetheringConnectivity(tester, tethered, true /* is4To6 */);
-
-        // Above has guaranteed that the found packet is an IPv6 packet without ether header.
-        return Struct.parse(Ipv6Header.class, ByteBuffer.wrap(expectedPacket)).srcIp;
+        runUdp4Test();
     }
 
     // Test network topology:
@@ -1073,4 +839,41 @@
                 REMOTE_NAT64_ADDR /* downloadSrcIp */, clatIp6 /* downloadDstIp */,
                 tester, true /* isClat */);
     }
+
+    private static final byte[] ZeroLengthDhcpPacket = new byte[] {
+            // scapy.Ether(
+            //   dst="ff:ff:ff:ff:ff:ff")
+            // scapy.IP(
+            //   dst="255.255.255.255")
+            // scapy.UDP(sport=68, dport=67)
+            /* Ethernet Header */
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+            (byte) 0xe0, (byte) 0x4f, (byte) 0x43, (byte) 0xe6, (byte) 0xfb, (byte) 0xd2,
+            (byte) 0x08, (byte) 0x00,
+            /* Ip header */
+            (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x1c, (byte) 0x00, (byte) 0x01,
+            (byte) 0x00, (byte) 0x00, (byte) 0x40, (byte) 0x11, (byte) 0xb6, (byte) 0x58,
+            (byte) 0x64, (byte) 0x4f, (byte) 0x60, (byte) 0x29, (byte) 0xff, (byte) 0xff,
+            (byte) 0xff, (byte) 0xff,
+            /* UDP header */
+            (byte) 0x00, (byte) 0x44, (byte) 0x00, (byte) 0x43,
+            (byte) 0x00, (byte) 0x08, (byte) 0x3a, (byte) 0xdf
+    };
+
+    @Test
+    public void testTetherZeroLengthDhcpPacket() throws Exception {
+        final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
+                toList(TEST_IP4_DNS));
+        tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
+
+        // Send a zero-length DHCP packet to upstream DHCP server.
+        final ByteBuffer packet = ByteBuffer.wrap(ZeroLengthDhcpPacket);
+        tester.sendUploadPacket(packet);
+
+        // Send DHCPDISCOVER packet from another downstream tethered device to verify that
+        // upstream DHCP server doesn't close the listening socket and stop reading, then we
+        // can still receive the next DHCP packet from server.
+        final MacAddress macAddress = MacAddress.fromString("11:22:33:44:55:66");
+        assertTrue(tester.testDhcpServerAlive(macAddress));
+    }
 }
diff --git a/Tethering/tests/mts/Android.bp b/Tethering/tests/mts/Android.bp
index ae36499..4f4b03c 100644
--- a/Tethering/tests/mts/Android.bp
+++ b/Tethering/tests/mts/Android.bp
@@ -33,6 +33,7 @@
     ],
 
     static_libs: [
+        "TetheringIntegrationTestsBaseLib",
         "androidx.test.rules",
         // mockito-target-extended-minus-junit4 used in this lib have dependency with
         // jni_libs libdexmakerjvmtiagent and libstaticjvmtiagent.
diff --git a/Tethering/tests/mts/AndroidManifest.xml b/Tethering/tests/mts/AndroidManifest.xml
index 6d2abca..42f2da9 100644
--- a/Tethering/tests/mts/AndroidManifest.xml
+++ b/Tethering/tests/mts/AndroidManifest.xml
@@ -27,8 +27,6 @@
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.tethering.mts"
                      android:label="MTS tests of android.tethering">
-        <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
     </instrumentation>
 
 </manifest>
diff --git a/Tethering/tests/mts/src/android/tethering/mts/MtsEthernetTetheringTest.java b/Tethering/tests/mts/src/android/tethering/mts/MtsEthernetTetheringTest.java
new file mode 100644
index 0000000..c2bc812
--- /dev/null
+++ b/Tethering/tests/mts/src/android/tethering/mts/MtsEthernetTetheringTest.java
@@ -0,0 +1,304 @@
+/*
+ * 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;
+
+import static android.Manifest.permission.DUMP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static com.android.testutils.DeviceInfoUtils.KVersion;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
+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 static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.net.TetheringTester.TetheredDevice;
+import android.os.Build;
+import android.os.VintfRuntimeInfo;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.BpfDump;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.bpf.Tether4Key;
+import com.android.net.module.util.bpf.Tether4Value;
+import com.android.net.module.util.bpf.TetherStatsKey;
+import com.android.net.module.util.bpf.TetherStatsValue;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DeviceInfoUtils;
+import com.android.testutils.DumpTestUtils;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class MtsEthernetTetheringTest extends EthernetTetheringTestBase {
+    @Rule
+    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
+    private static final String TAG = MtsEthernetTetheringTest.class.getSimpleName();
+
+    private static final int DUMP_POLLING_MAX_RETRY = 100;
+    private static final int DUMP_POLLING_INTERVAL_MS = 50;
+    // Kernel treats a confirmed UDP connection which active after two seconds as stream mode.
+    // See upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5.
+    private static final int UDP_STREAM_TS_MS = 2000;
+    // Give slack time for waiting UDP stream mode because handling conntrack event in user space
+    // may not in precise time. Used to reduce the flaky rate.
+    private static final int UDP_STREAM_SLACK_MS = 500;
+    // Per RX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
+    private static final int RX_UDP_PACKET_SIZE = 30;
+    private static final int RX_UDP_PACKET_COUNT = 456;
+    // Per TX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
+    private static final int TX_UDP_PACKET_SIZE = 30;
+    private static final int TX_UDP_PACKET_COUNT = 123;
+
+    private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
+    private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
+    private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
+    private static final String LINE_DELIMITER = "\\n";
+
+    private static boolean isUdpOffloadSupportedByKernel(final String kernelVersion) {
+        final KVersion current = DeviceInfoUtils.getMajorMinorSubminorVersion(kernelVersion);
+        return current.isInRange(new KVersion(4, 14, 222), new KVersion(4, 19, 0))
+                || current.isInRange(new KVersion(4, 19, 176), new KVersion(5, 4, 0))
+                || current.isAtLeast(new KVersion(5, 4, 98));
+    }
+
+    @Test
+    public void testIsUdpOffloadSupportedByKernel() throws Exception {
+        assertFalse(isUdpOffloadSupportedByKernel("4.14.221"));
+        assertTrue(isUdpOffloadSupportedByKernel("4.14.222"));
+        assertTrue(isUdpOffloadSupportedByKernel("4.16.0"));
+        assertTrue(isUdpOffloadSupportedByKernel("4.18.0"));
+        assertFalse(isUdpOffloadSupportedByKernel("4.19.0"));
+
+        assertFalse(isUdpOffloadSupportedByKernel("4.19.175"));
+        assertTrue(isUdpOffloadSupportedByKernel("4.19.176"));
+        assertTrue(isUdpOffloadSupportedByKernel("5.2.0"));
+        assertTrue(isUdpOffloadSupportedByKernel("5.3.0"));
+        assertFalse(isUdpOffloadSupportedByKernel("5.4.0"));
+
+        assertFalse(isUdpOffloadSupportedByKernel("5.4.97"));
+        assertTrue(isUdpOffloadSupportedByKernel("5.4.98"));
+        assertTrue(isUdpOffloadSupportedByKernel("5.10.0"));
+    }
+
+    private static void assumeKernelSupportBpfOffloadUdpV4() {
+        final String kernelVersion = VintfRuntimeInfo.getKernelVersion();
+        assumeTrue("Kernel version " + kernelVersion + " doesn't support IPv4 UDP BPF offload",
+                isUdpOffloadSupportedByKernel(kernelVersion));
+    }
+
+    @Test
+    public void testKernelSupportBpfOffloadUdpV4() throws Exception {
+        assumeKernelSupportBpfOffloadUdpV4();
+    }
+
+    private boolean isTetherConfigBpfOffloadEnabled() throws Exception {
+        final String dumpStr = runAsShell(DUMP, () ->
+                DumpTestUtils.dumpService(Context.TETHERING_SERVICE, "--short"));
+
+        // BPF offload tether config can be overridden by "config_tether_enable_bpf_offload" in
+        // packages/modules/Connectivity/Tethering/res/values/config.xml. OEM may disable config by
+        // RRO to override the enabled default value. Get the tethering config via dumpsys.
+        // $ dumpsys tethering
+        //   mIsBpfEnabled: true
+        boolean enabled = dumpStr.contains("mIsBpfEnabled: true");
+        if (!enabled) {
+            Log.d(TAG, "BPF offload tether config not enabled: " + dumpStr);
+        }
+        return enabled;
+    }
+
+    @Test
+    public void testTetherConfigBpfOffloadEnabled() throws Exception {
+        assumeTrue(isTetherConfigBpfOffloadEnabled());
+    }
+
+    @NonNull
+    private <K extends Struct, V extends Struct> HashMap<K, V> dumpAndParseRawMap(
+            Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
+            throws Exception {
+        final String[] args = new String[] {DUMPSYS_TETHERING_RAWMAP_ARG, mapArg};
+        final String rawMapStr = runAsShell(DUMP, () ->
+                DumpTestUtils.dumpService(Context.TETHERING_SERVICE, args));
+        final HashMap<K, V> map = new HashMap<>();
+
+        for (final String line : rawMapStr.split(LINE_DELIMITER)) {
+            final Pair<K, V> rule =
+                    BpfDump.fromBase64EncodedString(keyClass, valueClass, line.trim());
+            map.put(rule.first, rule.second);
+        }
+        return map;
+    }
+
+    @Nullable
+    private <K extends Struct, V extends Struct> HashMap<K, V> pollRawMapFromDump(
+            Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
+            throws Exception {
+        for (int retryCount = 0; retryCount < DUMP_POLLING_MAX_RETRY; retryCount++) {
+            final HashMap<K, V> map = dumpAndParseRawMap(keyClass, valueClass, mapArg);
+            if (!map.isEmpty()) return map;
+
+            Thread.sleep(DUMP_POLLING_INTERVAL_MS);
+        }
+
+        fail("Cannot get rules after " + DUMP_POLLING_MAX_RETRY * DUMP_POLLING_INTERVAL_MS + "ms");
+        return null;
+    }
+
+    // Test network topology:
+    //
+    //         public network (rawip)                 private network
+    //                   |                 UE                |
+    // +------------+    V    +------------+------------+    V    +------------+
+    // |   Sever    +---------+  Upstream  | Downstream +---------+   Client   |
+    // +------------+         +------------+------------+         +------------+
+    // remote ip              public ip                           private ip
+    // 8.8.8.8:443            <Upstream ip>:9876                  <TetheredDevice ip>:9876
+    //
+    private void runUdp4Test() throws Exception {
+        final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
+                toList(TEST_IP4_DNS));
+        final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
+
+        // TODO: remove the connectivity verification for upstream connected notification race.
+        // Because async upstream connected notification can't guarantee the tethering routing is
+        // ready to use. Need to test tethering connectivity before testing.
+        // For short term plan, consider using IPv6 RA to get MAC address because the prefix comes
+        // from upstream. That can guarantee that the routing is ready. Long term plan is that
+        // refactors upstream connected notification from async to sync.
+        probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
+
+        final MacAddress srcMac = tethered.macAddr;
+        final MacAddress dstMac = tethered.routerMacAddr;
+        final InetAddress remoteIp = REMOTE_IP4_ADDR;
+        final InetAddress tetheringUpstreamIp = TEST_IP4_ADDR.getAddress();
+        final InetAddress clientIp = tethered.ipv4Addr;
+        sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
+        sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
+
+        // Send second UDP packet in original direction.
+        // The BPF coordinator only offloads the ASSURED conntrack entry. The "request + reply"
+        // packets can make status IPS_SEEN_REPLY to be set. Need one more packet to make
+        // conntrack status IPS_ASSURED_BIT to be set. Note the third packet needs to delay
+        // 2 seconds because kernel monitors a UDP connection which still alive after 2 seconds
+        // and apply ASSURED flag.
+        // See kernel upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5 and
+        // nf_conntrack_udp_packet in net/netfilter/nf_conntrack_proto_udp.c
+        Thread.sleep(UDP_STREAM_TS_MS);
+        sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
+
+        // Give a slack time for handling conntrack event in user space.
+        Thread.sleep(UDP_STREAM_SLACK_MS);
+
+        // [1] Verify IPv4 upstream rule map.
+        final HashMap<Tether4Key, Tether4Value> upstreamMap = pollRawMapFromDump(
+                Tether4Key.class, Tether4Value.class, DUMPSYS_RAWMAP_ARG_UPSTREAM4);
+        assertNotNull(upstreamMap);
+        assertEquals(1, upstreamMap.size());
+
+        final Map.Entry<Tether4Key, Tether4Value> rule =
+                upstreamMap.entrySet().iterator().next();
+
+        final Tether4Key upstream4Key = rule.getKey();
+        assertEquals(IPPROTO_UDP, upstream4Key.l4proto);
+        assertTrue(Arrays.equals(tethered.ipv4Addr.getAddress(), upstream4Key.src4));
+        assertEquals(LOCAL_PORT, upstream4Key.srcPort);
+        assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(), upstream4Key.dst4));
+        assertEquals(REMOTE_PORT, upstream4Key.dstPort);
+
+        final Tether4Value upstream4Value = rule.getValue();
+        assertTrue(Arrays.equals(tetheringUpstreamIp.getAddress(),
+                InetAddress.getByAddress(upstream4Value.src46).getAddress()));
+        assertEquals(LOCAL_PORT, upstream4Value.srcPort);
+        assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(),
+                InetAddress.getByAddress(upstream4Value.dst46).getAddress()));
+        assertEquals(REMOTE_PORT, upstream4Value.dstPort);
+
+        // [2] Verify stats map.
+        // Transmit packets on both direction for verifying stats. Because we only care the
+        // packet count in stats test, we just reuse the existing packets to increaes
+        // the packet count on both direction.
+
+        // Send packets on original direction.
+        for (int i = 0; i < TX_UDP_PACKET_COUNT; i++) {
+            sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester,
+                    false /* is4To6 */);
+        }
+
+        // Send packets on reply direction.
+        for (int i = 0; i < RX_UDP_PACKET_COUNT; i++) {
+            sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
+        }
+
+        // Dump stats map to verify.
+        final HashMap<TetherStatsKey, TetherStatsValue> statsMap = pollRawMapFromDump(
+                TetherStatsKey.class, TetherStatsValue.class, DUMPSYS_RAWMAP_ARG_STATS);
+        assertNotNull(statsMap);
+        assertEquals(1, statsMap.size());
+
+        final Map.Entry<TetherStatsKey, TetherStatsValue> stats =
+                statsMap.entrySet().iterator().next();
+
+        // TODO: verify the upstream index in TetherStatsKey.
+
+        final TetherStatsValue statsValue = stats.getValue();
+        assertEquals(RX_UDP_PACKET_COUNT, statsValue.rxPackets);
+        assertEquals(RX_UDP_PACKET_COUNT * RX_UDP_PACKET_SIZE, statsValue.rxBytes);
+        assertEquals(0, statsValue.rxErrors);
+        assertEquals(TX_UDP_PACKET_COUNT, statsValue.txPackets);
+        assertEquals(TX_UDP_PACKET_COUNT * TX_UDP_PACKET_SIZE, statsValue.txBytes);
+        assertEquals(0, statsValue.txErrors);
+    }
+
+    /**
+     * BPF offload IPv4 UDP tethering test. Verify that UDP tethered packets are offloaded by BPF.
+     * Minimum test requirement:
+     * 1. S+ device.
+     * 2. Tethering config enables tethering BPF offload.
+     * 3. Kernel supports IPv4 UDP BPF offload. See #isUdpOffloadSupportedByKernel.
+     *
+     * TODO: consider enabling the test even tethering config disables BPF offload. See b/238288883
+     */
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testTetherBpfOffloadUdpV4() throws Exception {
+        assumeTrue("Tethering config disabled BPF offload", isTetherConfigBpfOffloadEnabled());
+        assumeKernelSupportBpfOffloadUdpV4();
+
+        runUdp4Test();
+    }
+}
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
index 23fb60c..81d4fbe 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
@@ -20,7 +20,7 @@
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.NETLINK_NETFILTER;
 
-import static com.android.net.module.util.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
+import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFSIZE;
 import static com.android.networkstack.tethering.OffloadHardwareInterface.IPCTNL_MSG_CT_NEW;
 import static com.android.networkstack.tethering.OffloadHardwareInterface.NFNL_SUBSYS_CTNETLINK;
 import static com.android.networkstack.tethering.OffloadHardwareInterface.NF_NETLINK_CONNTRACK_DESTROY;
@@ -41,7 +41,7 @@
 import com.android.net.module.util.SharedLog;
 import com.android.net.module.util.netlink.ConntrackMessage;
 import com.android.net.module.util.netlink.NetlinkMessage;
-import com.android.net.module.util.netlink.NetlinkSocket;
+import com.android.net.module.util.netlink.NetlinkUtils;
 import com.android.net.module.util.netlink.StructNlMsgHdr;
 
 import org.junit.Before;
@@ -80,7 +80,7 @@
         // Looper must be prepared here since AndroidJUnitRunner runs tests on separate threads.
         if (Looper.myLooper() == null) Looper.prepare();
 
-        mDeps = new OffloadHardwareInterface.Dependencies(mLog);
+        mDeps = new OffloadHardwareInterface.Dependencies(mHandler, mLog);
         mOffloadHw = new OffloadHardwareInterface(mHandler, mLog, mDeps);
     }
 
@@ -90,7 +90,7 @@
 
         // Loop until the socket is found (and return) or recvMessage throws an exception.
         while (true) {
-            final ByteBuffer buffer = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT);
+            final ByteBuffer buffer = NetlinkUtils.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT);
 
             // Parse all the netlink messages in the dump.
             // NetlinkMessage#parse returns null if the message is truncated or invalid.
@@ -106,6 +106,7 @@
                 ConntrackMessage.Tuple tuple = ctmsg.tupleOrig;
 
                 if (nlmsghdr.nlmsg_type == (NFNL_SUBSYS_CTNETLINK << 8 | IPCTNL_MSG_CT_NEW)
+                        && tuple != null
                         && tuple.protoNum == IPPROTO_TCP
                         && tuple.srcIp.equals(local.getAddress())
                         && tuple.dstIp.equals(remote.getAddress())
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index f0d9057..46e50ef 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -19,6 +19,8 @@
 import static android.net.INetd.IF_STATE_DOWN;
 import static android.net.INetd.IF_STATE_UP;
 import static android.net.RouteInfo.RTN_UNICAST;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
 import static android.net.TetheringManager.TETHERING_BLUETOOTH;
 import static android.net.TetheringManager.TETHERING_NCM;
 import static android.net.TetheringManager.TETHERING_USB;
@@ -48,6 +50,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
@@ -271,8 +274,8 @@
             dispatchTetherConnectionChanged(upstreamIface, lp, 0);
         }
         reset(mNetd, mCallback, mAddressCoordinator, mBpfCoordinator);
-        when(mAddressCoordinator.requestDownstreamAddress(any(), anyBoolean())).thenReturn(
-                mTestAddress);
+        when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
+                anyBoolean())).thenReturn(mTestAddress);
     }
 
     @SuppressWarnings("DoNotCall") // Ignore warning for synchronous to call to Thread.run()
@@ -293,8 +296,8 @@
     @Before public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog);
-        when(mAddressCoordinator.requestDownstreamAddress(any(), anyBoolean())).thenReturn(
-                mTestAddress);
+        when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
+                anyBoolean())).thenReturn(mTestAddress);
         when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(DEFAULT_USING_BPF_OFFLOAD);
         when(mTetherConfig.useLegacyDhcpServer()).thenReturn(false /* default value */);
 
@@ -428,7 +431,8 @@
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
         InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
         if (isAtLeastT()) {
-            inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
+            inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
+                    eq(CONNECTIVITY_SCOPE_GLOBAL), eq(true));
             inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
                     IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
         }
@@ -477,7 +481,8 @@
 
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
         InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
-        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
+        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
+                eq(CONNECTIVITY_SCOPE_GLOBAL), eq(true));
         inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
                 IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
         inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
@@ -498,7 +503,8 @@
 
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);
         InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
-        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
+        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
+                eq(CONNECTIVITY_SCOPE_LOCAL), eq(true));
         inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
                   IFACE_NAME.equals(cfg.ifName) && assertNotContainsFlag(cfg.flags, IF_STATE_UP)));
         inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
@@ -766,7 +772,8 @@
         final ArgumentCaptor<LinkProperties> lpCaptor =
                 ArgumentCaptor.forClass(LinkProperties.class);
         InOrder inOrder = inOrder(mNetd, mCallback, mAddressCoordinator);
-        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
+        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
+                eq(CONNECTIVITY_SCOPE_LOCAL), eq(true));
         inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
         // One for ipv4 route, one for ipv6 link local route.
         inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
@@ -779,12 +786,13 @@
         // Simulate the DHCP server receives DHCPDECLINE on MirrorLink and then signals
         // onNewPrefixRequest callback.
         final LinkAddress newAddress = new LinkAddress("192.168.100.125/24");
-        when(mAddressCoordinator.requestDownstreamAddress(any(), anyBoolean())).thenReturn(
-                newAddress);
+        when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(),
+                anyBoolean())).thenReturn(newAddress);
         eventCallbacks.onNewPrefixRequest(new IpPrefix("192.168.42.0/24"));
         mLooper.dispatchAll();
 
-        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(false));
+        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(),
+                eq(CONNECTIVITY_SCOPE_LOCAL), eq(false));
         inOrder.verify(mNetd).tetherApplyDnsInterfaces();
         inOrder.verify(mCallback).updateLinkProperties(eq(mIpServer), lpCaptor.capture());
         verifyNoMoreInteractions(mCallback);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 53984a8..4f32f3c 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -16,6 +16,8 @@
 
 package com.android.networkstack.tethering;
 
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
 import static android.net.NetworkStats.METERED_NO;
 import static android.net.NetworkStats.ROAMING_NO;
@@ -32,6 +34,7 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_MIN_MTU;
 import static com.android.net.module.util.ip.ConntrackMonitor.ConntrackEvent;
 import static com.android.net.module.util.netlink.ConntrackMessage.DYING_MASK;
 import static com.android.net.module.util.netlink.ConntrackMessage.ESTABLISHED_MASK;
@@ -41,6 +44,7 @@
 import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_DELETE;
 import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW;
 import static com.android.networkstack.tethering.BpfCoordinator.CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS;
+import static com.android.networkstack.tethering.BpfCoordinator.INVALID_MTU;
 import static com.android.networkstack.tethering.BpfCoordinator.NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED;
 import static com.android.networkstack.tethering.BpfCoordinator.NF_CONNTRACK_UDP_TIMEOUT_STREAM;
 import static com.android.networkstack.tethering.BpfCoordinator.NON_OFFLOADED_UPSTREAM_IPV4_TCP_PORTS;
@@ -75,6 +79,7 @@
 import android.app.usage.NetworkStatsManager;
 import android.net.INetd;
 import android.net.InetAddresses;
+import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.MacAddress;
@@ -87,6 +92,7 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.test.TestLooper;
+import android.util.SparseArray;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -109,7 +115,7 @@
 import com.android.net.module.util.ip.ConntrackMonitor.ConntrackEventConsumer;
 import com.android.net.module.util.netlink.ConntrackMessage;
 import com.android.net.module.util.netlink.NetlinkConstants;
-import com.android.net.module.util.netlink.NetlinkSocket;
+import com.android.net.module.util.netlink.NetlinkUtils;
 import com.android.networkstack.tethering.BpfCoordinator.BpfConntrackEventConsumer;
 import com.android.networkstack.tethering.BpfCoordinator.ClientInfo;
 import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
@@ -154,11 +160,13 @@
 
     private static final int INVALID_IFINDEX = 0;
     private static final int UPSTREAM_IFINDEX = 1001;
-    private static final int UPSTREAM_IFINDEX2 = 1002;
-    private static final int DOWNSTREAM_IFINDEX = 1003;
-    private static final int DOWNSTREAM_IFINDEX2 = 1004;
+    private static final int UPSTREAM_XLAT_IFINDEX = 1002;
+    private static final int UPSTREAM_IFINDEX2 = 1003;
+    private static final int DOWNSTREAM_IFINDEX = 2001;
+    private static final int DOWNSTREAM_IFINDEX2 = 2002;
 
     private static final String UPSTREAM_IFACE = "rmnet0";
+    private static final String UPSTREAM_XLAT_IFACE = "v4-rmnet0";
     private static final String UPSTREAM_IFACE2 = "wlan0";
 
     private static final MacAddress DOWNSTREAM_MAC = MacAddress.fromString("12:34:56:78:90:ab");
@@ -181,6 +189,10 @@
     private static final Inet4Address PRIVATE_ADDR2 =
             (Inet4Address) InetAddresses.parseNumericAddress("192.168.90.12");
 
+    private static final Inet4Address XLAT_LOCAL_IPV4ADDR =
+            (Inet4Address) InetAddresses.parseNumericAddress("192.0.0.46");
+    private static final IpPrefix NAT64_IP_PREFIX = new IpPrefix("64:ff9b::/96");
+
     // Generally, public port and private port are the same in the NAT conntrack message.
     // TODO: consider using different private port and public port for testing.
     private static final short REMOTE_PORT = (short) 443;
@@ -192,6 +204,10 @@
     private static final InterfaceParams UPSTREAM_IFACE_PARAMS = new InterfaceParams(
             UPSTREAM_IFACE, UPSTREAM_IFINDEX, null /* macAddr, rawip */,
             NetworkStackConstants.ETHER_MTU);
+    private static final InterfaceParams UPSTREAM_XLAT_IFACE_PARAMS = new InterfaceParams(
+            UPSTREAM_XLAT_IFACE, UPSTREAM_XLAT_IFINDEX, null /* macAddr, rawip */,
+            NetworkStackConstants.ETHER_MTU - 28
+            /* mtu delta from external/android-clat/clatd.c */);
     private static final InterfaceParams UPSTREAM_IFACE_PARAMS2 = new InterfaceParams(
             UPSTREAM_IFACE2, UPSTREAM_IFINDEX2, MacAddress.fromString("44:55:66:00:00:0c"),
             NetworkStackConstants.ETHER_MTU);
@@ -283,6 +299,11 @@
             private int mDstPort = REMOTE_PORT;
             private long mLastUsed = 0;
 
+            public Builder setPmtu(short pmtu) {
+                mPmtu = pmtu;
+                return this;
+            }
+
             public Tether4Value build() {
                 return new Tether4Value(mOif, mEthDstMac, mEthSrcMac, mEthProto, mPmtu,
                         mSrc46, mDst46, mSrcPort, mDstPort, mLastUsed);
@@ -303,6 +324,11 @@
             private int mDstPort = PRIVATE_PORT;
             private long mLastUsed = 0;
 
+            public Builder setPmtu(short pmtu) {
+                mPmtu = pmtu;
+                return this;
+            }
+
             public Tether4Value build() {
                 return new Tether4Value(mOif, mEthDstMac, mEthSrcMac, mEthProto, mPmtu,
                         mSrc46, mDst46, mSrcPort, mDstPort, mLastUsed);
@@ -375,6 +401,7 @@
     private HashMap<IpServer, HashMap<Inet4Address, ClientInfo>> mTetherClients;
 
     private long mElapsedRealtimeNanos = 0;
+    private int mMtu = NetworkStackConstants.ETHER_MTU;
     private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
             ArgumentCaptor.forClass(ArrayList.class);
     private final TestLooper mTestLooper = new TestLooper();
@@ -430,6 +457,10 @@
                         return mElapsedRealtimeNanos;
                     }
 
+                    public int getNetworkInterfaceMtu(@NonNull String iface) {
+                        return mMtu;
+                    }
+
                     @Nullable
                     public IBpfMap<Tether4Key, Tether4Value> getBpfDownstream4Map() {
                         return mBpfDownstream4Map;
@@ -1518,6 +1549,7 @@
         final LinkProperties lp = new LinkProperties();
         lp.setInterfaceName(upstreamInfo.interfaceParams.name);
         lp.addLinkAddress(new LinkAddress(upstreamInfo.address, 32 /* prefix length */));
+        lp.setMtu(mMtu);
         final NetworkCapabilities capabilities = new NetworkCapabilities()
                 .addTransportType(upstreamInfo.transportType);
         coordinator.updateUpstreamNetworkState(new UpstreamNetworkState(lp, capabilities,
@@ -1697,9 +1729,9 @@
         final long validTime = (CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS - 1) * 1_000_000L;
         final long expiredTime = (CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS + 1) * 1_000_000L;
 
-        // Static mocking for NetlinkSocket.
+        // Static mocking for NetlinkUtils.
         MockitoSession mockSession = ExtendedMockito.mockitoSession()
-                .mockStatic(NetlinkSocket.class)
+                .mockStatic(NetlinkUtils.class)
                 .startMocking();
         try {
             final BpfCoordinator coordinator = makeBpfCoordinator();
@@ -1711,8 +1743,8 @@
             setElapsedRealtimeNanos(expiredTime);
             mTestLooper.moveTimeForward(CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS);
             waitForIdle();
-            ExtendedMockito.verifyNoMoreInteractions(staticMockMarker(NetlinkSocket.class));
-            ExtendedMockito.clearInvocations(staticMockMarker(NetlinkSocket.class));
+            ExtendedMockito.verifyNoMoreInteractions(staticMockMarker(NetlinkUtils.class));
+            ExtendedMockito.clearInvocations(staticMockMarker(NetlinkUtils.class));
 
             // [2] Refresh conntrack timeout.
             setElapsedRealtimeNanos(validTime);
@@ -1724,19 +1756,19 @@
             final byte[] expectedNetlinkUdp = ConntrackMessage.newIPv4TimeoutUpdateRequest(
                     IPPROTO_UDP, PRIVATE_ADDR, (int) PRIVATE_PORT, REMOTE_ADDR,
                     (int) REMOTE_PORT, NF_CONNTRACK_UDP_TIMEOUT_STREAM);
-            ExtendedMockito.verify(() -> NetlinkSocket.sendOneShotKernelMessage(
+            ExtendedMockito.verify(() -> NetlinkUtils.sendOneShotKernelMessage(
                     eq(NETLINK_NETFILTER), eq(expectedNetlinkTcp)));
-            ExtendedMockito.verify(() -> NetlinkSocket.sendOneShotKernelMessage(
+            ExtendedMockito.verify(() -> NetlinkUtils.sendOneShotKernelMessage(
                     eq(NETLINK_NETFILTER), eq(expectedNetlinkUdp)));
-            ExtendedMockito.verifyNoMoreInteractions(staticMockMarker(NetlinkSocket.class));
-            ExtendedMockito.clearInvocations(staticMockMarker(NetlinkSocket.class));
+            ExtendedMockito.verifyNoMoreInteractions(staticMockMarker(NetlinkUtils.class));
+            ExtendedMockito.clearInvocations(staticMockMarker(NetlinkUtils.class));
 
             // [3] Don't refresh conntrack timeout if polling stopped.
             coordinator.stopPolling();
             mTestLooper.moveTimeForward(CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS);
             waitForIdle();
-            ExtendedMockito.verifyNoMoreInteractions(staticMockMarker(NetlinkSocket.class));
-            ExtendedMockito.clearInvocations(staticMockMarker(NetlinkSocket.class));
+            ExtendedMockito.verifyNoMoreInteractions(staticMockMarker(NetlinkUtils.class));
+            ExtendedMockito.clearInvocations(staticMockMarker(NetlinkUtils.class));
         } finally {
             mockSession.finishMocking();
         }
@@ -2108,7 +2140,7 @@
     @Test
     public void testIpv6ForwardingRuleToString() throws Exception {
         final Ipv6ForwardingRule rule = buildTestForwardingRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A);
-        assertEquals("upstreamIfindex: 1001, downstreamIfindex: 1003, address: 2001:db8::1, "
+        assertEquals("upstreamIfindex: 1001, downstreamIfindex: 2001, address: 2001:db8::1, "
                 + "srcMac: 12:34:56:78:90:ab, dstMac: 00:00:00:00:00:0a", rule.toString());
     }
 
@@ -2195,4 +2227,238 @@
 
         verifyDump(coordinator);
     }
+
+    private void verifyAddTetherOffloadRule4Mtu(final int ifaceMtu, final boolean isKernelMtu,
+            final int expectedMtu) throws Exception {
+        // BpfCoordinator#updateUpstreamNetworkState geta mtu from LinkProperties. If not found,
+        // try to get from kernel.
+        if (isKernelMtu) {
+            // LinkProperties mtu is invalid and kernel mtu is valid.
+            mMtu = INVALID_MTU;
+            doReturn(ifaceMtu).when(mDeps).getNetworkInterfaceMtu(any());
+        } else {
+            // LinkProperties mtu is valid and kernel mtu is invalid.
+            mMtu = ifaceMtu;
+            doReturn(INVALID_MTU).when(mDeps).getNetworkInterfaceMtu(any());
+        }
+
+        final BpfCoordinator coordinator = makeBpfCoordinator();
+        initBpfCoordinatorForRule4(coordinator);
+
+        final Tether4Key expectedUpstream4KeyTcp = new TestUpstream4Key.Builder()
+                .setProto(IPPROTO_TCP)
+                .build();
+        final Tether4Key expectedDownstream4KeyTcp = new TestDownstream4Key.Builder()
+                .setProto(IPPROTO_TCP)
+                .build();
+        final Tether4Value expectedUpstream4ValueTcp = new TestUpstream4Value.Builder()
+                .setPmtu((short) expectedMtu)
+                .build();
+        final Tether4Value expectedDownstream4ValueTcp = new TestDownstream4Value.Builder()
+                .setPmtu((short) expectedMtu)
+                .build();
+
+        mConsumer.accept(new TestConntrackEvent.Builder()
+                .setMsgType(IPCTNL_MSG_CT_NEW)
+                .setProto(IPPROTO_TCP)
+                .build());
+        verify(mBpfUpstream4Map)
+                .insertEntry(eq(expectedUpstream4KeyTcp), eq(expectedUpstream4ValueTcp));
+        verify(mBpfDownstream4Map)
+                .insertEntry(eq(expectedDownstream4KeyTcp), eq(expectedDownstream4ValueTcp));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testAddTetherOffloadRule4LowMtuFromLinkProperties() throws Exception {
+        verifyAddTetherOffloadRule4Mtu(
+                IPV4_MIN_MTU, false /* isKernelMtu */, IPV4_MIN_MTU /* expectedMtu */);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testAddTetherOffloadRule4LowMtuFromKernel() throws Exception {
+        verifyAddTetherOffloadRule4Mtu(
+                IPV4_MIN_MTU, true /* isKernelMtu */, IPV4_MIN_MTU /* expectedMtu */);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testAddTetherOffloadRule4LessThanIpv4MinMtu() throws Exception {
+        verifyAddTetherOffloadRule4Mtu(
+                IPV4_MIN_MTU - 1, false /* isKernelMtu */, IPV4_MIN_MTU /* expectedMtu */);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testAddTetherOffloadRule4InvalidMtu() throws Exception {
+        verifyAddTetherOffloadRule4Mtu(INVALID_MTU, false /* isKernelMtu */,
+                NetworkStackConstants.ETHER_MTU /* expectedMtu */);
+    }
+
+    private static LinkProperties buildUpstreamLinkProperties(final String interfaceName,
+            boolean withIPv4, boolean withIPv6, boolean with464xlat) {
+        final LinkProperties prop = new LinkProperties();
+        prop.setInterfaceName(interfaceName);
+
+        if (withIPv4) {
+            // Assign the address no matter what the interface is. It is okay for now because
+            // only single upstream is available.
+            // TODO: consider to assign address by interface once we need to test two or more
+            // BPF supported upstreams or multi upstreams are supported.
+            prop.addLinkAddress(new LinkAddress(PUBLIC_ADDR, 24));
+        }
+
+        if (withIPv6) {
+            // TODO: make this to be constant. Currently, no test who uses this function cares what
+            // the upstream IPv6 address is.
+            prop.addLinkAddress(new LinkAddress("2001:db8::5175:15ca/64"));
+        }
+
+        if (with464xlat) {
+            final String clatInterface = "v4-" + interfaceName;
+            final LinkProperties stackedLink = new LinkProperties();
+            stackedLink.setInterfaceName(clatInterface);
+            stackedLink.addLinkAddress(new LinkAddress(XLAT_LOCAL_IPV4ADDR, 24));
+            prop.addStackedLink(stackedLink);
+            prop.setNat64Prefix(NAT64_IP_PREFIX);
+        }
+
+        return prop;
+    }
+
+    private void verifyIpv4Upstream(
+            @NonNull final HashMap<Inet4Address, Integer> ipv4UpstreamIndices,
+            @NonNull final SparseArray<String> interfaceNames) {
+        assertEquals(1, ipv4UpstreamIndices.size());
+        Integer upstreamIndex = ipv4UpstreamIndices.get(PUBLIC_ADDR);
+        assertNotNull(upstreamIndex);
+        assertEquals(UPSTREAM_IFINDEX, upstreamIndex.intValue());
+        assertEquals(1, interfaceNames.size());
+        assertTrue(interfaceNames.contains(UPSTREAM_IFINDEX));
+    }
+
+    private void verifyUpdateUpstreamNetworkState()
+            throws Exception {
+        final BpfCoordinator coordinator = makeBpfCoordinator();
+        final HashMap<Inet4Address, Integer> ipv4UpstreamIndices =
+                coordinator.getIpv4UpstreamIndicesForTesting();
+        assertTrue(ipv4UpstreamIndices.isEmpty());
+        final SparseArray<String> interfaceNames =
+                coordinator.getInterfaceNamesForTesting();
+        assertEquals(0, interfaceNames.size());
+
+        // Verify the following are added or removed after upstream changes.
+        // - BpfCoordinator#mIpv4UpstreamIndices (for building IPv4 offload rules)
+        // - BpfCoordinator#mInterfaceNames (for updating limit)
+        //
+        // +-------+-------+-----------------------+
+        // | Test  | Up    |       Protocol        |
+        // | Case# | stream+-------+-------+-------+
+        // |       |       | IPv4  | IPv6  | Xlat  |
+        // +-------+-------+-------+-------+-------+
+        // |   1   | Cell  |   O   |       |       |
+        // +-------+-------+-------+-------+-------+
+        // |   2   | Cell  |       |   O   |       |
+        // +-------+-------+-------+-------+-------+
+        // |   3   | Cell  |   O   |   O   |       |
+        // +-------+-------+-------+-------+-------+
+        // |   4   |   -   |       |       |       |
+        // +-------+-------+-------+-------+-------+
+        // |       | Cell  |   O   |       |       |
+        // |       +-------+-------+-------+-------+
+        // |   5   | Cell  |       |   O   |   O   | <-- doesn't support offload (xlat)
+        // |       +-------+-------+-------+-------+
+        // |       | Cell  |   O   |       |       |
+        // +-------+-------+-------+-------+-------+
+        // |   6   | Wifi  |   O   |   O   |       | <-- doesn't support offload (ether ip)
+        // +-------+-------+-------+-------+-------+
+
+        // [1] Mobile IPv4 only
+        coordinator.addUpstreamNameToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE);
+        doReturn(UPSTREAM_IFACE_PARAMS).when(mDeps).getInterfaceParams(UPSTREAM_IFACE);
+        final UpstreamNetworkState mobileIPv4UpstreamState = new UpstreamNetworkState(
+                buildUpstreamLinkProperties(UPSTREAM_IFACE,
+                        true /* IPv4 */, false /* IPv6 */, false /* 464xlat */),
+                new NetworkCapabilities().addTransportType(TRANSPORT_CELLULAR),
+                new Network(TEST_NET_ID));
+        coordinator.updateUpstreamNetworkState(mobileIPv4UpstreamState);
+        verifyIpv4Upstream(ipv4UpstreamIndices, interfaceNames);
+
+        // [2] Mobile IPv6 only
+        final UpstreamNetworkState mobileIPv6UpstreamState = new UpstreamNetworkState(
+                buildUpstreamLinkProperties(UPSTREAM_IFACE,
+                        false /* IPv4 */, true /* IPv6 */, false /* 464xlat */),
+                new NetworkCapabilities().addTransportType(TRANSPORT_CELLULAR),
+                new Network(TEST_NET_ID));
+        coordinator.updateUpstreamNetworkState(mobileIPv6UpstreamState);
+        assertTrue(ipv4UpstreamIndices.isEmpty());
+        assertEquals(1, interfaceNames.size());
+        assertTrue(interfaceNames.contains(UPSTREAM_IFINDEX));
+
+        // [3] Mobile IPv4 and IPv6
+        final UpstreamNetworkState mobileDualStackUpstreamState = new UpstreamNetworkState(
+                buildUpstreamLinkProperties(UPSTREAM_IFACE,
+                        true /* IPv4 */, true /* IPv6 */, false /* 464xlat */),
+                new NetworkCapabilities().addTransportType(TRANSPORT_CELLULAR),
+                new Network(TEST_NET_ID));
+        coordinator.updateUpstreamNetworkState(mobileDualStackUpstreamState);
+        verifyIpv4Upstream(ipv4UpstreamIndices, interfaceNames);
+
+        // [4] Lost upstream
+        coordinator.updateUpstreamNetworkState(null);
+        assertTrue(ipv4UpstreamIndices.isEmpty());
+        assertEquals(1, interfaceNames.size());
+        assertTrue(interfaceNames.contains(UPSTREAM_IFINDEX));
+
+        // [5] verify xlat interface
+        // Expect that xlat interface information isn't added to mapping.
+        doReturn(UPSTREAM_XLAT_IFACE_PARAMS).when(mDeps).getInterfaceParams(
+                UPSTREAM_XLAT_IFACE);
+        final UpstreamNetworkState mobile464xlatUpstreamState = new UpstreamNetworkState(
+                buildUpstreamLinkProperties(UPSTREAM_IFACE,
+                        false /* IPv4 */, true /* IPv6 */, true /* 464xlat */),
+                new NetworkCapabilities().addTransportType(TRANSPORT_CELLULAR),
+                new Network(TEST_NET_ID));
+
+        // Need to add a valid IPv4 upstream to verify that xlat interface doesn't support.
+        // Mobile IPv4 only
+        coordinator.updateUpstreamNetworkState(mobileIPv4UpstreamState);
+        verifyIpv4Upstream(ipv4UpstreamIndices, interfaceNames);
+
+        // Mobile IPv6 and xlat
+        // IpServer doesn't add xlat interface mapping via #addUpstreamNameToLookupTable on
+        // S and T devices.
+        coordinator.updateUpstreamNetworkState(mobile464xlatUpstreamState);
+        // Upstream IPv4 address mapping is removed because xlat interface is not supported.
+        assertTrue(ipv4UpstreamIndices.isEmpty());
+        assertEquals(1, interfaceNames.size());
+        assertTrue(interfaceNames.contains(UPSTREAM_IFINDEX));
+
+        // Need to add a valid IPv4 upstream to verify that wifi interface doesn't support.
+        // Mobile IPv4 only
+        coordinator.updateUpstreamNetworkState(mobileIPv4UpstreamState);
+        verifyIpv4Upstream(ipv4UpstreamIndices, interfaceNames);
+
+        // [6] Wifi IPv4 and IPv6
+        // Expect that upstream index map is cleared because ether ip is not supported.
+        coordinator.addUpstreamNameToLookupTable(UPSTREAM_IFINDEX2, UPSTREAM_IFACE2);
+        doReturn(UPSTREAM_IFACE_PARAMS2).when(mDeps).getInterfaceParams(UPSTREAM_IFACE2);
+        final UpstreamNetworkState wifiDualStackUpstreamState = new UpstreamNetworkState(
+                buildUpstreamLinkProperties(UPSTREAM_IFACE2,
+                        true /* IPv4 */, true /* IPv6 */, false /* 464xlat */),
+                new NetworkCapabilities().addTransportType(TRANSPORT_WIFI),
+                new Network(TEST_NET_ID2));
+        coordinator.updateUpstreamNetworkState(wifiDualStackUpstreamState);
+        assertTrue(ipv4UpstreamIndices.isEmpty());
+        assertEquals(2, interfaceNames.size());
+        assertTrue(interfaceNames.contains(UPSTREAM_IFINDEX));
+        assertTrue(interfaceNames.contains(UPSTREAM_IFINDEX2));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    public void testUpdateUpstreamNetworkState() throws Exception {
+        verifyUpdateUpstreamNetworkState();
+    }
 }
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/ConnectedClientsTrackerTest.kt b/Tethering/tests/unit/src/com/android/networkstack/tethering/ConnectedClientsTrackerTest.kt
index d915354..2dd9f91 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/ConnectedClientsTrackerTest.kt
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/ConnectedClientsTrackerTest.kt
@@ -24,19 +24,25 @@
 import android.net.TetheringManager.TETHERING_WIFI
 import android.net.ip.IpServer
 import android.net.wifi.WifiClient
+import android.os.Build
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.mock
-import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertTrue
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class ConnectedClientsTrackerTest {
+    @get:Rule
+    val ignoreRule = DevSdkIgnoreRule()
 
     private val server1 = mock(IpServer::class.java)
     private val server2 = mock(IpServer::class.java)
@@ -70,55 +76,122 @@
 
     @Test
     fun testUpdateConnectedClients() {
+        doReturn(IpServer.STATE_TETHERED).`when`(server1).servingMode()
+        doReturn(IpServer.STATE_TETHERED).`when`(server2).servingMode()
+        runUpdateConnectedClientsTest(isGlobal = true)
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    fun testUpdateConnectedClients_LocalOnly() {
+        doReturn(IpServer.STATE_LOCAL_ONLY).`when`(server1).servingMode()
+        doReturn(IpServer.STATE_LOCAL_ONLY).`when`(server2).servingMode()
+        runUpdateConnectedClientsTest(isGlobal = false)
+    }
+
+    fun runUpdateConnectedClientsTest(isGlobal: Boolean) {
         doReturn(emptyList<TetheredClient>()).`when`(server1).allLeases
         doReturn(emptyList<TetheredClient>()).`when`(server2).allLeases
 
         val tracker = ConnectedClientsTracker(clock)
-        assertFalse(tracker.updateConnectedClients(servers, null))
+        assertFalse(tracker.updateConnectedClients(servers, null, null))
 
         // Obtain a lease for client 1
         doReturn(listOf(client1)).`when`(server1).allLeases
-        assertSameClients(listOf(client1), assertNewClients(tracker, servers, listOf(wifiClient1)))
+        if (isGlobal) {
+            assertSameClients(listOf(client1), assertNewClients(tracker, servers,
+                    wifiClients = listOf(wifiClient1)))
+        } else {
+            assertSameClients(listOf(client1), assertNewClients(tracker, servers,
+                    localOnlyClients = listOf(wifiClient1)))
+        }
 
         // Client 2 L2-connected, no lease yet
         val client2WithoutAddr = TetheredClient(client2Addr, emptyList(), TETHERING_WIFI)
-        assertSameClients(listOf(client1, client2WithoutAddr),
-                assertNewClients(tracker, servers, listOf(wifiClient1, wifiClient2)))
+        if (isGlobal) {
+            assertSameClients(listOf(client1, client2WithoutAddr), assertNewClients(
+                    tracker, servers, wifiClients = listOf(wifiClient1, wifiClient2)))
+        } else {
+            assertSameClients(listOf(client1, client2WithoutAddr), assertNewClients(
+                    tracker, servers, localOnlyClients = listOf(wifiClient1, wifiClient2)))
+        }
 
         // Client 2 lease obtained
         doReturn(listOf(client1, client2)).`when`(server1).allLeases
-        assertSameClients(listOf(client1, client2), assertNewClients(tracker, servers, null))
+        assertSameClients(listOf(client1, client2), assertNewClients(tracker, servers))
 
         // Client 3 lease obtained
         doReturn(listOf(client3)).`when`(server2).allLeases
-        assertSameClients(listOf(client1, client2, client3),
-                assertNewClients(tracker, servers, null))
+        assertSameClients(listOf(client1, client2, client3), assertNewClients(tracker, servers))
 
-        // Client 2 L2-disconnected
-        assertSameClients(listOf(client1, client3),
-                assertNewClients(tracker, servers, listOf(wifiClient1)))
-
-        // Client 1 L2-disconnected
-        assertSameClients(listOf(client3), assertNewClients(tracker, servers, emptyList()))
-
-        // Client 1 comes back
-        assertSameClients(listOf(client1, client3),
-                assertNewClients(tracker, servers, listOf(wifiClient1)))
+        if (isGlobal) {
+            // Client 2 L2-disconnected
+            assertSameClients(listOf(client1, client3),
+                    assertNewClients(tracker, servers, wifiClients = listOf(wifiClient1)))
+            // Client 1 L2-disconnected
+            assertSameClients(listOf(client3), assertNewClients(tracker, servers,
+                    wifiClients = emptyList()))
+            // Client 1 comes back
+            assertSameClients(listOf(client1, client3),
+                    assertNewClients(tracker, servers, wifiClients = listOf(wifiClient1)))
+        } else {
+            // Client 2 L2-disconnected
+            assertSameClients(listOf(client1, client3),
+                    assertNewClients(tracker, servers, localOnlyClients = listOf(wifiClient1)))
+            // Client 1 L2-disconnected
+            assertSameClients(listOf(client3),
+                    assertNewClients(tracker, servers, localOnlyClients = emptyList()))
+            // Client 1 comes back
+            assertSameClients(listOf(client1, client3),
+                    assertNewClients(tracker, servers, localOnlyClients = listOf(wifiClient1)))
+        }
 
         // Leases lost, client 1 still L2-connected
         doReturn(emptyList<TetheredClient>()).`when`(server1).allLeases
         doReturn(emptyList<TetheredClient>()).`when`(server2).allLeases
         assertSameClients(listOf(TetheredClient(client1Addr, emptyList(), TETHERING_WIFI)),
-                assertNewClients(tracker, servers, null))
+                assertNewClients(tracker, servers))
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    fun testLocalOnlyAndTetheredHotspotClients() {
+        val tracker = ConnectedClientsTracker(clock)
+        doReturn(IpServer.STATE_LOCAL_ONLY).`when`(server1).servingMode()
+        doReturn(IpServer.STATE_TETHERED).`when`(server2).servingMode()
+
+        // Client 1 connected to server1 (LOHS)
+        doReturn(listOf(client1)).`when`(server1).allLeases
+        doReturn(emptyList<TetheredClient>()).`when`(server2).allLeases
+        assertSameClients(listOf(client1), assertNewClients(tracker, servers,
+                localOnlyClients = listOf(wifiClient1)))
+
+        // Client 2 connected to server2 (wifi Tethering)
+        doReturn(listOf(client2)).`when`(server2).allLeases
+        assertSameClients(listOf(client1, client2), assertNewClients(tracker, servers,
+                listOf(wifiClient2), listOf(wifiClient1)))
+
+        // Client 2 L2-disconnected but lease doesn't expired yet
+        assertSameClients(listOf(client1), assertNewClients(tracker, servers,
+                wifiClients = emptyList()))
+
+        // Client 1 lease lost but still L2-connected
+        doReturn(emptyList<TetheredClient>()).`when`(server1).allLeases
+        val client1WithoutAddr = TetheredClient(client1Addr, emptyList(), TETHERING_WIFI)
+        assertSameClients(listOf(client1WithoutAddr), assertNewClients(tracker, servers))
+
+        // Client 1 L2-disconnected
+        assertSameClients(emptyList(), assertNewClients(tracker, servers,
+                localOnlyClients = emptyList()))
     }
 
     @Test
     fun testUpdateConnectedClients_LeaseExpiration() {
+        doReturn(IpServer.STATE_TETHERED).`when`(server1).servingMode()
+        doReturn(IpServer.STATE_TETHERED).`when`(server2).servingMode()
         val tracker = ConnectedClientsTracker(clock)
         doReturn(listOf(client1, client2)).`when`(server1).allLeases
         doReturn(listOf(client3)).`when`(server2).allLeases
         assertSameClients(listOf(client1, client2, client3), assertNewClients(
-                tracker, servers, listOf(wifiClient1, wifiClient2)))
+                tracker, servers, wifiClients = listOf(wifiClient1, wifiClient2)))
 
         clock.time += 20
         // Client 3 has no remaining lease: removed
@@ -131,15 +204,16 @@
                         // Only the "t + 30" address is left, the "t + 10" address expired
                         listOf(client2Exp30AddrInfo),
                         TETHERING_WIFI))
-        assertSameClients(expectedClients, assertNewClients(tracker, servers, null))
+        assertSameClients(expectedClients, assertNewClients(tracker, servers))
     }
 
     private fun assertNewClients(
         tracker: ConnectedClientsTracker,
         ipServers: Iterable<IpServer>,
-        wifiClients: List<WifiClient>?
+        wifiClients: List<WifiClient>? = null,
+        localOnlyClients: List<WifiClient>? = null
     ): List<TetheredClient> {
-        assertTrue(tracker.updateConnectedClients(ipServers, wifiClients))
+        assertTrue(tracker.updateConnectedClients(ipServers, wifiClients, localOnlyClients))
         return tracker.lastTetheredClients
     }
 
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
index e4263db..c2e1617 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
@@ -213,7 +213,8 @@
         }
 
         @Override
-        PendingIntent createRecheckAlarmIntent() {
+        PendingIntent createRecheckAlarmIntent(final String pkgName) {
+            assertEquals(TEST_PACKAGE_NAME, pkgName);
             return mAlarmIntent;
         }
     }
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
index 0d686ed..9e287a0 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java
@@ -19,22 +19,26 @@
 import android.content.Context;
 import android.content.res.Resources;
 
+import androidx.annotation.NonNull;
+
 import com.android.net.module.util.SharedLog;
 
 /** FakeTetheringConfiguration is used to override static method for testing. */
 public class FakeTetheringConfiguration extends TetheringConfiguration {
     FakeTetheringConfiguration(Context ctx, SharedLog log, int id) {
-        super(ctx, log, id);
-    }
+        super(ctx, log, id, new Dependencies() {
+            @Override
+            boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
+                    @NonNull String name, @NonNull String moduleName, boolean defaultEnabled) {
+                return defaultEnabled;
+            }
 
-    @Override
-    protected String getDeviceConfigProperty(final String name) {
-        return null;
-    }
-
-    @Override
-    protected boolean isFeatureEnabled(Context ctx, String namespace, String featureVersionFlag) {
-        return false;
+            @Override
+            boolean getDeviceConfigBoolean(@NonNull String namespace, @NonNull String name,
+                    boolean defaultValue) {
+                return defaultValue;
+            }
+        });
     }
 
     @Override
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
index faca1c8..36c15a7 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
@@ -31,8 +31,8 @@
 import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_IFACE;
 import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_UID;
 import static com.android.networkstack.tethering.OffloadHardwareInterface.ForwardedStats;
-import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0;
-import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_1;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_0;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_1;
 import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
 import static com.android.testutils.MiscAsserts.assertContainsAll;
 import static com.android.testutils.MiscAsserts.assertThrows;
@@ -79,6 +79,7 @@
 
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.net.module.util.SharedLog;
+import com.android.networkstack.tethering.OffloadHardwareInterface.OffloadHalCallback;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.TestableNetworkStatsProviderCbBinder;
@@ -125,8 +126,8 @@
     private OffloadController.OffloadTetheringStatsProvider mTetherStatsProvider;
     private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
             ArgumentCaptor.forClass(ArrayList.class);
-    private final ArgumentCaptor<OffloadHardwareInterface.ControlCallback> mControlCallbackCaptor =
-            ArgumentCaptor.forClass(OffloadHardwareInterface.ControlCallback.class);
+    private final ArgumentCaptor<OffloadHalCallback> mOffloadHalCallbackCaptor =
+            ArgumentCaptor.forClass(OffloadHalCallback.class);
     private MockContentResolver mContentResolver;
     private final TestLooper mTestLooper = new TestLooper();
     private OffloadController.Dependencies mDeps = new OffloadController.Dependencies() {
@@ -151,10 +152,9 @@
         FakeSettingsProvider.clearSettingsProvider();
     }
 
-    private void setupFunctioningHardwareInterface(int controlVersion) {
-        when(mHardware.initOffloadConfig()).thenReturn(true);
-        when(mHardware.initOffloadControl(mControlCallbackCaptor.capture()))
-                .thenReturn(controlVersion);
+    private void setupFunctioningHardwareInterface(int offloadHalVersion) {
+        when(mHardware.initOffload(mOffloadHalCallbackCaptor.capture()))
+                .thenReturn(offloadHalVersion);
         when(mHardware.setUpstreamParameters(anyString(), any(), any(), any())).thenReturn(true);
         when(mHardware.getForwardedStats(any())).thenReturn(new ForwardedStats());
         when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true);
@@ -192,9 +192,9 @@
     @Test
     public void testStartStop() throws Exception {
         stopOffloadController(
-                startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/));
+                startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/));
         stopOffloadController(
-                startOffloadController(OFFLOAD_HAL_VERSION_1_1, true /*expectStart*/));
+                startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_1, true /*expectStart*/));
     }
 
     @NonNull
@@ -206,9 +206,8 @@
 
         final InOrder inOrder = inOrder(mHardware);
         inOrder.verify(mHardware, times(1)).getDefaultTetherOffloadDisabled();
-        inOrder.verify(mHardware, times(expectStart ? 1 : 0)).initOffloadConfig();
-        inOrder.verify(mHardware, times(expectStart ? 1 : 0)).initOffloadControl(
-                any(OffloadHardwareInterface.ControlCallback.class));
+        inOrder.verify(mHardware, times(expectStart ? 1 : 0)).initOffload(
+                any(OffloadHalCallback.class));
         inOrder.verifyNoMoreInteractions();
         // Clear counters only instead of whole mock to preserve the mocking setup.
         clearInvocations(mHardware);
@@ -218,7 +217,7 @@
     private void stopOffloadController(final OffloadController offload) throws Exception {
         final InOrder inOrder = inOrder(mHardware);
         offload.stop();
-        inOrder.verify(mHardware, times(1)).stopOffloadControl();
+        inOrder.verify(mHardware, times(1)).stopOffload();
         inOrder.verifyNoMoreInteractions();
         reset(mHardware);
     }
@@ -228,7 +227,7 @@
         when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(1);
         assertThrows(SettingNotFoundException.class, () ->
                 Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED));
-        startOffloadController(OFFLOAD_HAL_VERSION_1_0, false /*expectStart*/);
+        startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, false /*expectStart*/);
     }
 
     @Test
@@ -236,26 +235,26 @@
         when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(0);
         assertThrows(SettingNotFoundException.class, () ->
                 Settings.Global.getInt(mContentResolver, TETHER_OFFLOAD_DISABLED));
-        startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
+        startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/);
     }
 
     @Test
     public void testSettingsAllowsStart() throws Exception {
         Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0);
-        startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
+        startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/);
     }
 
     @Test
     public void testSettingsDisablesStart() throws Exception {
         Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 1);
-        startOffloadController(OFFLOAD_HAL_VERSION_1_0, false /*expectStart*/);
+        startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, false /*expectStart*/);
     }
 
     @Test
     public void testSetUpstreamLinkPropertiesWorking() throws Exception {
         enableOffload();
         final OffloadController offload =
-                startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
+                startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/);
 
         // In reality, the UpstreamNetworkMonitor would have passed down to us
         // a covering set of local prefixes representing a minimum essential
@@ -426,7 +425,7 @@
     public void testGetForwardedStats() throws Exception {
         enableOffload();
         final OffloadController offload =
-                startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
+                startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/);
 
         final String ethernetIface = "eth1";
         final String mobileIface = "rmnet_data0";
@@ -521,11 +520,11 @@
         // Verify the OffloadController is called by R framework, where the framework doesn't send
         // warning.
         // R only uses HAL 1.0.
-        checkSetDataWarningAndLimit(false, OFFLOAD_HAL_VERSION_1_0);
+        checkSetDataWarningAndLimit(false, OFFLOAD_HAL_VERSION_HIDL_1_0);
         // Verify the OffloadController is called by S+ framework, where the framework sends
         // warning along with limit.
-        checkSetDataWarningAndLimit(true, OFFLOAD_HAL_VERSION_1_0);
-        checkSetDataWarningAndLimit(true, OFFLOAD_HAL_VERSION_1_1);
+        checkSetDataWarningAndLimit(true, OFFLOAD_HAL_VERSION_HIDL_1_0);
+        checkSetDataWarningAndLimit(true, OFFLOAD_HAL_VERSION_HIDL_1_1);
     }
 
     private void checkSetDataWarningAndLimit(boolean isProviderSetWarning, int controlVersion)
@@ -550,7 +549,7 @@
         when(mHardware.setDataWarningAndLimit(anyString(), anyLong(), anyLong())).thenReturn(true);
         offload.setUpstreamLinkProperties(lp);
         // Applying an interface sends the initial quota to the hardware.
-        if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) {
+        if (controlVersion >= OFFLOAD_HAL_VERSION_HIDL_1_1) {
             inOrder.verify(mHardware).setDataWarningAndLimit(ethernetIface, Long.MAX_VALUE,
                     Long.MAX_VALUE);
         } else {
@@ -576,7 +575,7 @@
             mTetherStatsProvider.onSetLimit(ethernetIface, ethernetLimit);
         }
         waitForIdle();
-        if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) {
+        if (controlVersion >= OFFLOAD_HAL_VERSION_HIDL_1_1) {
             inOrder.verify(mHardware).setDataWarningAndLimit(ethernetIface, Long.MAX_VALUE,
                     ethernetLimit);
         } else {
@@ -591,7 +590,7 @@
             mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit);
         }
         waitForIdle();
-        if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) {
+        if (controlVersion >= OFFLOAD_HAL_VERSION_HIDL_1_1) {
             inOrder.verify(mHardware, never()).setDataWarningAndLimit(anyString(), anyLong(),
                     anyLong());
         } else {
@@ -603,7 +602,7 @@
         lp.setInterfaceName(mobileIface);
         offload.setUpstreamLinkProperties(lp);
         waitForIdle();
-        if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) {
+        if (controlVersion >= OFFLOAD_HAL_VERSION_HIDL_1_1) {
             inOrder.verify(mHardware).setDataWarningAndLimit(mobileIface,
                     isProviderSetWarning ? mobileWarning : Long.MAX_VALUE,
                     mobileLimit);
@@ -620,7 +619,7 @@
             mTetherStatsProvider.onSetLimit(mobileIface, NetworkStatsProvider.QUOTA_UNLIMITED);
         }
         waitForIdle();
-        if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) {
+        if (controlVersion >= OFFLOAD_HAL_VERSION_HIDL_1_1) {
             inOrder.verify(mHardware).setDataWarningAndLimit(mobileIface, Long.MAX_VALUE,
                     Long.MAX_VALUE);
         } else {
@@ -655,15 +654,15 @@
         }
         waitForIdle();
         inOrder.verify(mHardware).getForwardedStats(ethernetIface);
-        inOrder.verify(mHardware).stopOffloadControl();
+        inOrder.verify(mHardware).stopOffload();
     }
 
     @Test
     public void testDataWarningAndLimitCallback_LimitReached() throws Exception {
         enableOffload();
-        startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
+        startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/);
 
-        final OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
+        final OffloadHalCallback callback = mOffloadHalCallbackCaptor.getValue();
         callback.onStoppedLimitReached();
         mTetherStatsProviderCb.expectNotifyStatsUpdated();
 
@@ -679,8 +678,8 @@
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.R)  // HAL 1.1 is only supported from S
     public void testDataWarningAndLimitCallback_WarningReached() throws Exception {
-        startOffloadController(OFFLOAD_HAL_VERSION_1_1, true /*expectStart*/);
-        final OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
+        startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_1, true /*expectStart*/);
+        final OffloadHalCallback callback = mOffloadHalCallbackCaptor.getValue();
         callback.onWarningReached();
         mTetherStatsProviderCb.expectNotifyStatsUpdated();
 
@@ -695,7 +694,7 @@
     public void testAddRemoveDownstreams() throws Exception {
         enableOffload();
         final OffloadController offload =
-                startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
+                startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/);
         final InOrder inOrder = inOrder(mHardware);
 
         // Tethering makes several calls to setLocalPrefixes() before add/remove
@@ -710,14 +709,14 @@
         usbLinkProperties.addRoute(
                 new RouteInfo(new IpPrefix(USB_PREFIX), null, null, RTN_UNICAST));
         offload.notifyDownstreamLinkProperties(usbLinkProperties);
-        inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, USB_PREFIX);
+        inOrder.verify(mHardware, times(1)).addDownstream(RNDIS0, USB_PREFIX);
         inOrder.verifyNoMoreInteractions();
 
         // [2] Routes for IPv6 link-local prefixes should never be added.
         usbLinkProperties.addRoute(
                 new RouteInfo(new IpPrefix(IPV6_LINKLOCAL), null, null, RTN_UNICAST));
         offload.notifyDownstreamLinkProperties(usbLinkProperties);
-        inOrder.verify(mHardware, never()).addDownstreamPrefix(eq(RNDIS0), anyString());
+        inOrder.verify(mHardware, never()).addDownstream(eq(RNDIS0), anyString());
         inOrder.verifyNoMoreInteractions();
 
         // [3] Add an IPv6 prefix for good measure. Only new offload-able
@@ -726,14 +725,14 @@
         usbLinkProperties.addRoute(
                 new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX), null, null, RTN_UNICAST));
         offload.notifyDownstreamLinkProperties(usbLinkProperties);
-        inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX);
+        inOrder.verify(mHardware, times(1)).addDownstream(RNDIS0, IPV6_DOC_PREFIX);
         inOrder.verifyNoMoreInteractions();
 
         // [4] Adding addresses doesn't affect notifyDownstreamLinkProperties().
         // The address is passed in by a separate setLocalPrefixes() invocation.
         usbLinkProperties.addLinkAddress(new LinkAddress("2001:db8::2/64"));
         offload.notifyDownstreamLinkProperties(usbLinkProperties);
-        inOrder.verify(mHardware, never()).addDownstreamPrefix(eq(RNDIS0), anyString());
+        inOrder.verify(mHardware, never()).addDownstream(eq(RNDIS0), anyString());
 
         // [5] Differences in local routes are converted into addDownstream()
         // and removeDownstream() invocations accordingly.
@@ -742,8 +741,8 @@
         usbLinkProperties.addRoute(
                 new RouteInfo(new IpPrefix(IPV6_DISCARD_PREFIX), null, null, RTN_UNICAST));
         offload.notifyDownstreamLinkProperties(usbLinkProperties);
-        inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX);
-        inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DISCARD_PREFIX);
+        inOrder.verify(mHardware, times(1)).removeDownstream(RNDIS0, IPV6_DOC_PREFIX);
+        inOrder.verify(mHardware, times(1)).addDownstream(RNDIS0, IPV6_DISCARD_PREFIX);
         inOrder.verifyNoMoreInteractions();
 
         // [6] Removing a downstream interface which was never added causes no
@@ -753,8 +752,8 @@
 
         // [7] Removing an active downstream removes all remaining prefixes.
         offload.removeDownstreamInterface(RNDIS0);
-        inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, USB_PREFIX);
-        inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, IPV6_DISCARD_PREFIX);
+        inOrder.verify(mHardware, times(1)).removeDownstream(RNDIS0, USB_PREFIX);
+        inOrder.verify(mHardware, times(1)).removeDownstream(RNDIS0, IPV6_DISCARD_PREFIX);
         inOrder.verifyNoMoreInteractions();
     }
 
@@ -762,7 +761,7 @@
     public void testControlCallbackOnStoppedUnsupportedFetchesAllStats() throws Exception {
         enableOffload();
         final OffloadController offload =
-                startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
+                startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/);
 
         // Pretend to set a few different upstreams (only the interface name
         // matters for this test; we're ignoring IP and route information).
@@ -776,7 +775,7 @@
         // that happen with setUpstreamParameters().
         clearInvocations(mHardware);
 
-        OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
+        OffloadHalCallback callback = mOffloadHalCallbackCaptor.getValue();
         callback.onStoppedUnsupported();
 
         // Verify forwarded stats behaviour.
@@ -793,7 +792,7 @@
             throws Exception {
         enableOffload();
         final OffloadController offload =
-                startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
+                startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/);
 
         // Pretend to set a few different upstreams (only the interface name
         // matters for this test; we're ignoring IP and route information).
@@ -840,7 +839,7 @@
         // that happen with setUpstreamParameters().
         clearInvocations(mHardware);
 
-        OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
+        OffloadHalCallback callback = mOffloadHalCallbackCaptor.getValue();
         callback.onSupportAvailable();
 
         // Verify forwarded stats behaviour.
@@ -859,8 +858,8 @@
                 // into OffloadController proper. After this, also check for:
                 //     "192.168.43.1/32", "2001:2::1/128", "2001:2::2/128"
                 "127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64");
-        verify(mHardware, times(1)).addDownstreamPrefix(WLAN0, "192.168.43.0/24");
-        verify(mHardware, times(1)).addDownstreamPrefix(WLAN0, "2001:2::/64");
+        verify(mHardware, times(1)).addDownstream(WLAN0, "192.168.43.0/24");
+        verify(mHardware, times(1)).addDownstream(WLAN0, "2001:2::/64");
         verify(mHardware, times(1)).setUpstreamParameters(eq(RMNET0), any(), any(), any());
         verify(mHardware, times(1)).setDataLimit(eq(RMNET0), anyLong());
         verifyNoMoreInteractions(mHardware);
@@ -871,7 +870,7 @@
         enableOffload();
         setOffloadPollInterval(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
         final OffloadController offload =
-                startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/);
+                startOffloadController(OFFLOAD_HAL_VERSION_HIDL_1_0, true /*expectStart*/);
 
         // Initialize with fake eth upstream.
         final String ethernetIface = "eth1";
@@ -925,7 +924,7 @@
         offload.setUpstreamLinkProperties(makeEthernetLinkProperties());
         mTetherStatsProvider.onSetAlert(0);
         waitForIdle();
-        if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) {
+        if (controlVersion >= OFFLOAD_HAL_VERSION_HIDL_1_1) {
             mTetherStatsProviderCb.assertNoCallback();
         } else {
             mTetherStatsProviderCb.expectNotifyAlertReached();
@@ -935,7 +934,7 @@
 
     @Test
     public void testSoftwarePollingUsed() throws Exception {
-        checkSoftwarePollingUsed(OFFLOAD_HAL_VERSION_1_0);
-        checkSoftwarePollingUsed(OFFLOAD_HAL_VERSION_1_1);
+        checkSoftwarePollingUsed(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        checkSoftwarePollingUsed(OFFLOAD_HAL_VERSION_HIDL_1_1);
     }
 }
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHalAidlImplTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHalAidlImplTest.java
new file mode 100644
index 0000000..c9ce64f
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHalAidlImplTest.java
@@ -0,0 +1,390 @@
+/*
+ * 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 com.android.networkstack.tethering;
+
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_AIDL;
+
+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 static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.tetheroffload.ForwardedStats;
+import android.hardware.tetheroffload.IOffload;
+import android.hardware.tetheroffload.IPv4AddrPortPair;
+import android.hardware.tetheroffload.ITetheringOffloadCallback;
+import android.hardware.tetheroffload.NatTimeoutUpdate;
+import android.hardware.tetheroffload.NetworkProtocol;
+import android.hardware.tetheroffload.OffloadCallbackEvent;
+import android.os.Handler;
+import android.os.NativeHandle;
+import android.os.ParcelFileDescriptor;
+import android.os.ServiceSpecificException;
+import android.os.test.TestLooper;
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.SharedLog;
+import com.android.networkstack.tethering.OffloadHardwareInterface.OffloadHalCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.MockitoAnnotations;
+
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class OffloadHalAidlImplTest {
+    private static final String RMNET0 = "test_rmnet_data0";
+
+    private final SharedLog mLog = new SharedLog("test");
+    private final TestLooper mTestLooper = new TestLooper();
+
+    private IOffload mIOffloadMock;
+    private OffloadHalAidlImpl mIOffloadHal;
+    private ITetheringOffloadCallback mTetheringOffloadCallback;
+    private OffloadHalCallback mOffloadHalCallback;
+
+    private void initAndValidateOffloadHal(boolean initSuccess)
+            throws Exception {
+        final FileDescriptor fd1 = new FileDescriptor();
+        final FileDescriptor fd2 = new FileDescriptor();
+        final NativeHandle handle1 = new NativeHandle(fd1, true);
+        final NativeHandle handle2 = new NativeHandle(fd2, true);
+        final ArgumentCaptor<ParcelFileDescriptor> fdCaptor1 =
+                ArgumentCaptor.forClass(ParcelFileDescriptor.class);
+        final ArgumentCaptor<ParcelFileDescriptor> fdCaptor2 =
+                ArgumentCaptor.forClass(ParcelFileDescriptor.class);
+        final ArgumentCaptor<ITetheringOffloadCallback> offloadCallbackCaptor =
+                ArgumentCaptor.forClass(ITetheringOffloadCallback.class);
+        if (initSuccess) {
+            doNothing().when(mIOffloadMock).initOffload(any(), any(), any());
+        } else {
+            doThrow(new IllegalStateException()).when(mIOffloadMock).initOffload(any(), any(),
+                    any());
+        }
+        assertEquals(mIOffloadHal.initOffload(handle1, handle2, mOffloadHalCallback),
+                     initSuccess);
+        verify(mIOffloadMock).initOffload(fdCaptor1.capture(), fdCaptor2.capture(),
+                                          offloadCallbackCaptor.capture());
+        assertEquals(fdCaptor1.getValue().getFd(), fd1.getInt$());
+        assertEquals(fdCaptor2.getValue().getFd(), fd2.getInt$());
+        mTetheringOffloadCallback = offloadCallbackCaptor.getValue();
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mIOffloadMock = mock(IOffload.class);
+        mIOffloadHal = new OffloadHalAidlImpl(OFFLOAD_HAL_VERSION_AIDL, mIOffloadMock,
+                new Handler(mTestLooper.getLooper()), mLog);
+        mOffloadHalCallback = spy(new OffloadHalCallback());
+    }
+
+    @Test
+    public void testInitOffloadSuccess() throws Exception {
+        initAndValidateOffloadHal(true /* initSuccess */);
+    }
+
+    @Test
+    public void testInitOffloadFailure() throws Exception {
+        initAndValidateOffloadHal(false /* initSuccess */);
+    }
+
+    @Test
+    public void testStopOffloadSuccess() throws Exception {
+        initAndValidateOffloadHal(true);
+        doNothing().when(mIOffloadMock).stopOffload();
+        assertTrue(mIOffloadHal.stopOffload());
+        verify(mIOffloadMock).stopOffload();
+    }
+
+    @Test
+    public void testStopOffloadFailure() throws Exception {
+        initAndValidateOffloadHal(true);
+        doThrow(new IllegalStateException()).when(mIOffloadMock).stopOffload();
+        assertFalse(mIOffloadHal.stopOffload());
+    }
+
+    private void doTestGetForwardedStats(boolean expectSuccess) throws Exception {
+        initAndValidateOffloadHal(true);
+        final ForwardedStats returnStats = new ForwardedStats();
+        if (expectSuccess) {
+            returnStats.rxBytes = 12345;
+            returnStats.txBytes = 67890;
+            when(mIOffloadMock.getForwardedStats(anyString())).thenReturn(returnStats);
+        } else {
+            when(mIOffloadMock.getForwardedStats(anyString()))
+                    .thenThrow(new ServiceSpecificException(IOffload.ERROR_CODE_UNUSED));
+        }
+        final OffloadHardwareInterface.ForwardedStats stats =
+                mIOffloadHal.getForwardedStats(RMNET0);
+        verify(mIOffloadMock).getForwardedStats(eq(RMNET0));
+        assertNotNull(stats);
+        assertEquals(stats.rxBytes, returnStats.rxBytes);
+        assertEquals(stats.txBytes, returnStats.txBytes);
+    }
+
+    @Test
+    public void testGetForwardedStatsSuccess() throws Exception {
+        doTestGetForwardedStats(true);
+    }
+
+    @Test
+    public void testGetForwardedStatsFailure() throws Exception {
+        doTestGetForwardedStats(false);
+    }
+
+    private void doTestSetLocalPrefixes(boolean expectSuccess) throws Exception {
+        initAndValidateOffloadHal(true);
+        final ArrayList<String> localPrefixes = new ArrayList<>();
+        localPrefixes.add("127.0.0.0/8");
+        localPrefixes.add("fe80::/64");
+        final String[] localPrefixesArray =
+                localPrefixes.toArray(new String[localPrefixes.size()]);
+        if (expectSuccess) {
+            doNothing().when(mIOffloadMock).setLocalPrefixes(any());
+        } else {
+            doThrow(new IllegalArgumentException()).when(mIOffloadMock).setLocalPrefixes(any());
+        }
+        assertEquals(expectSuccess, mIOffloadHal.setLocalPrefixes(localPrefixes));
+        verify(mIOffloadMock).setLocalPrefixes(eq(localPrefixesArray));
+    }
+
+    @Test
+    public void testSetLocalPrefixesSuccess() throws Exception {
+        doTestSetLocalPrefixes(true);
+    }
+
+    @Test
+    public void testSetLocalPrefixesFailure() throws Exception {
+        doTestSetLocalPrefixes(false);
+    }
+
+    private void doTestSetDataLimit(boolean expectSuccess) throws Exception {
+        initAndValidateOffloadHal(true);
+        final long limit = 12345;
+        if (expectSuccess) {
+            doNothing().when(mIOffloadMock).setDataWarningAndLimit(anyString(), anyLong(),
+                    anyLong());
+        } else {
+            doThrow(new IllegalArgumentException())
+                    .when(mIOffloadMock).setDataWarningAndLimit(anyString(), anyLong(), anyLong());
+        }
+        assertEquals(expectSuccess, mIOffloadHal.setDataLimit(RMNET0, limit));
+        verify(mIOffloadMock).setDataWarningAndLimit(eq(RMNET0), eq(Long.MAX_VALUE), eq(limit));
+    }
+
+    @Test
+    public void testSetDataLimitSuccess() throws Exception {
+        doTestSetDataLimit(true);
+    }
+
+    @Test
+    public void testSetDataLimitFailure() throws Exception {
+        doTestSetDataLimit(false);
+    }
+
+    private void doTestSetDataWarningAndLimit(boolean expectSuccess) throws Exception {
+        initAndValidateOffloadHal(true);
+        final long warning = 12345;
+        final long limit = 67890;
+        if (expectSuccess) {
+            doNothing().when(mIOffloadMock).setDataWarningAndLimit(anyString(), anyLong(),
+                    anyLong());
+        } else {
+            doThrow(new IllegalArgumentException())
+                    .when(mIOffloadMock).setDataWarningAndLimit(anyString(), anyLong(), anyLong());
+        }
+        assertEquals(expectSuccess, mIOffloadHal.setDataWarningAndLimit(RMNET0, warning, limit));
+        verify(mIOffloadMock).setDataWarningAndLimit(eq(RMNET0), eq(warning), eq(limit));
+    }
+
+    @Test
+    public void testSetDataWarningAndLimitSuccess() throws Exception {
+        doTestSetDataWarningAndLimit(true);
+    }
+
+    @Test
+    public void testSetDataWarningAndLimitFailure() throws Exception {
+        doTestSetDataWarningAndLimit(false);
+    }
+
+    private void doTestSetUpstreamParameters(boolean expectSuccess) throws Exception {
+        initAndValidateOffloadHal(true);
+        final String v4addr = "192.168.10.1";
+        final String v4gateway = "192.168.10.255";
+        final ArrayList<String> v6gws = new ArrayList<>(0);
+        v6gws.add("2001:db8::1");
+        String[] v6gwsArray = v6gws.toArray(new String[v6gws.size()]);
+        if (expectSuccess) {
+            doNothing().when(mIOffloadMock).setUpstreamParameters(anyString(), anyString(),
+                    anyString(), any());
+        } else {
+            doThrow(new IllegalArgumentException()).when(mIOffloadMock).setUpstreamParameters(
+                    anyString(), anyString(), anyString(), any());
+        }
+        assertEquals(expectSuccess, mIOffloadHal.setUpstreamParameters(RMNET0, v4addr, v4gateway,
+                  v6gws));
+        verify(mIOffloadMock).setUpstreamParameters(eq(RMNET0), eq(v4addr), eq(v4gateway),
+                  eq(v6gwsArray));
+    }
+
+    @Test
+    public void testSetUpstreamParametersSuccess() throws Exception {
+        doTestSetUpstreamParameters(true);
+    }
+
+    @Test
+    public void testSetUpstreamParametersFailure() throws Exception {
+        doTestSetUpstreamParameters(false);
+    }
+
+    private void doTestAddDownstream(boolean expectSuccess) throws Exception {
+        initAndValidateOffloadHal(true);
+        final String ifName = "wlan1";
+        final String prefix = "192.168.43.0/24";
+        if (expectSuccess) {
+            doNothing().when(mIOffloadMock).addDownstream(anyString(), anyString());
+        } else {
+            doThrow(new IllegalStateException()).when(mIOffloadMock).addDownstream(anyString(),
+                    anyString());
+        }
+        assertEquals(expectSuccess, mIOffloadHal.addDownstream(ifName, prefix));
+        verify(mIOffloadMock).addDownstream(eq(ifName), eq(prefix));
+    }
+
+    @Test
+    public void testAddDownstreamSuccess() throws Exception {
+        doTestAddDownstream(true);
+    }
+
+    @Test
+    public void testAddDownstreamFailure() throws Exception {
+        doTestAddDownstream(false);
+    }
+
+    private void doTestRemoveDownstream(boolean expectSuccess) throws Exception {
+        initAndValidateOffloadHal(true);
+        final String ifName = "wlan1";
+        final String prefix = "192.168.43.0/24";
+        if (expectSuccess) {
+            doNothing().when(mIOffloadMock).removeDownstream(anyString(), anyString());
+        } else {
+            doThrow(new IllegalArgumentException()).when(mIOffloadMock).removeDownstream(
+                    anyString(), anyString());
+        }
+        assertEquals(expectSuccess, mIOffloadHal.removeDownstream(ifName, prefix));
+        verify(mIOffloadMock).removeDownstream(eq(ifName), eq(prefix));
+    }
+
+    @Test
+    public void testRemoveDownstreamSuccess() throws Exception {
+        doTestRemoveDownstream(true);
+    }
+
+    @Test
+    public void testRemoveDownstreamFailure() throws Exception {
+        doTestRemoveDownstream(false);
+    }
+
+    @Test
+    public void testTetheringOffloadCallback() throws Exception {
+        initAndValidateOffloadHal(true);
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STARTED);
+        mTestLooper.dispatchAll();
+        final InOrder inOrder = inOrder(mOffloadHalCallback);
+        inOrder.verify(mOffloadHalCallback).onStarted();
+        inOrder.verifyNoMoreInteractions();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mOffloadHalCallback).onStoppedError();
+        inOrder.verifyNoMoreInteractions();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mOffloadHalCallback).onStoppedUnsupported();
+        inOrder.verifyNoMoreInteractions();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mOffloadHalCallback).onSupportAvailable();
+        inOrder.verifyNoMoreInteractions();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mOffloadHalCallback).onStoppedLimitReached();
+        inOrder.verifyNoMoreInteractions();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_WARNING_REACHED);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mOffloadHalCallback).onWarningReached();
+        inOrder.verifyNoMoreInteractions();
+
+        final NatTimeoutUpdate tcpParams = buildNatTimeoutUpdate(NetworkProtocol.TCP);
+        mTetheringOffloadCallback.updateTimeout(tcpParams);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mOffloadHalCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_TCP),
+                eq(tcpParams.src.addr),
+                eq(tcpParams.src.port),
+                eq(tcpParams.dst.addr),
+                eq(tcpParams.dst.port));
+        inOrder.verifyNoMoreInteractions();
+
+        final NatTimeoutUpdate udpParams = buildNatTimeoutUpdate(NetworkProtocol.UDP);
+        mTetheringOffloadCallback.updateTimeout(udpParams);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mOffloadHalCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_UDP),
+                eq(udpParams.src.addr),
+                eq(udpParams.src.port),
+                eq(udpParams.dst.addr),
+                eq(udpParams.dst.port));
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    private NatTimeoutUpdate buildNatTimeoutUpdate(final int proto) {
+        final NatTimeoutUpdate params = new NatTimeoutUpdate();
+        params.proto = proto;
+        params.src = new IPv4AddrPortPair();
+        params.dst = new IPv4AddrPortPair();
+        params.src.addr = "192.168.43.200";
+        params.src.port = 100;
+        params.dst.addr = "172.50.46.169";
+        params.dst.port = 150;
+        return params;
+    }
+}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHalHidlImplTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHalHidlImplTest.java
new file mode 100644
index 0000000..6fdab5a
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHalHidlImplTest.java
@@ -0,0 +1,359 @@
+/*
+ * 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 com.android.networkstack.tethering;
+
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_0;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_1;
+import static com.android.networkstack.tethering.util.TetheringUtils.uint16;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
+import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
+import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
+import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
+import android.hardware.tetheroffload.control.V1_1.ITetheringOffloadCallback;
+import android.hardware.tetheroffload.control.V1_1.OffloadCallbackEvent;
+import android.os.Handler;
+import android.os.NativeHandle;
+import android.os.test.TestLooper;
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.SharedLog;
+import com.android.networkstack.tethering.OffloadHardwareInterface.ForwardedStats;
+import com.android.networkstack.tethering.OffloadHardwareInterface.OffloadHalCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.MockitoAnnotations;
+
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class OffloadHalHidlImplTest {
+    private static final String RMNET0 = "test_rmnet_data0";
+
+    private final SharedLog mLog = new SharedLog("test");
+    private final TestLooper mTestLooper = new TestLooper();
+
+    private OffloadHalHidlImpl mIOffloadHal;
+    private IOffloadConfig mIOffloadConfigMock;
+    private IOffloadControl mIOffloadControlMock;
+    private ITetheringOffloadCallback mTetheringOffloadCallback;
+    private OffloadHalCallback mOffloadHalCallback;
+
+    private void createAndInitOffloadHal(int version) throws Exception {
+        final FileDescriptor fd1 = new FileDescriptor();
+        final FileDescriptor fd2 = new FileDescriptor();
+        final NativeHandle handle1 = new NativeHandle(fd1, true);
+        final NativeHandle handle2 = new NativeHandle(fd2, true);
+        mIOffloadConfigMock = mock(IOffloadConfig.class);
+        switch (version) {
+            case OFFLOAD_HAL_VERSION_HIDL_1_0:
+                mIOffloadControlMock = mock(IOffloadControl.class);
+                break;
+            case OFFLOAD_HAL_VERSION_HIDL_1_1:
+                mIOffloadControlMock = mock(
+                        android.hardware.tetheroffload.control.V1_1.IOffloadControl.class);
+                break;
+            default:
+                fail("Nonexistent HAL version");
+                return;
+        }
+        mIOffloadHal = new OffloadHalHidlImpl(version, mIOffloadConfigMock,
+                mIOffloadControlMock, new Handler(mTestLooper.getLooper()), mLog);
+        mIOffloadHal.initOffload(handle1, handle2, mOffloadHalCallback);
+
+        final ArgumentCaptor<NativeHandle> nativeHandleCaptor1 =
+                ArgumentCaptor.forClass(NativeHandle.class);
+        final ArgumentCaptor<NativeHandle> nativeHandleCaptor2 =
+                ArgumentCaptor.forClass(NativeHandle.class);
+        final ArgumentCaptor<ITetheringOffloadCallback> offloadCallbackCaptor =
+                ArgumentCaptor.forClass(ITetheringOffloadCallback.class);
+        verify(mIOffloadConfigMock).setHandles(nativeHandleCaptor1.capture(),
+                nativeHandleCaptor2.capture(), any());
+        verify(mIOffloadControlMock).initOffload(offloadCallbackCaptor.capture(), any());
+        assertEquals(nativeHandleCaptor1.getValue().getFileDescriptor().getInt$(),
+                handle1.getFileDescriptor().getInt$());
+        assertEquals(nativeHandleCaptor2.getValue().getFileDescriptor().getInt$(),
+                handle2.getFileDescriptor().getInt$());
+        mTetheringOffloadCallback = offloadCallbackCaptor.getValue();
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mOffloadHalCallback = spy(new OffloadHalCallback());
+    }
+
+    @Test
+    public void testGetForwardedStats() throws Exception {
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        final long rxBytes = 12345;
+        final long txBytes = 67890;
+        doAnswer(invocation -> {
+            ((IOffloadControl.getForwardedStatsCallback) invocation.getArgument(1))
+                    .onValues(rxBytes, txBytes);
+            return null;
+        }).when(mIOffloadControlMock).getForwardedStats(eq(RMNET0), any());
+        final ForwardedStats stats = mIOffloadHal.getForwardedStats(RMNET0);
+        verify(mIOffloadControlMock).getForwardedStats(eq(RMNET0), any());
+        assertNotNull(stats);
+        assertEquals(rxBytes, stats.rxBytes);
+        assertEquals(txBytes, stats.txBytes);
+    }
+
+    private void doTestSetLocalPrefixes(boolean expectSuccess) throws Exception {
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        final ArrayList<String> localPrefixes = new ArrayList<>();
+        localPrefixes.add("127.0.0.0/8");
+        localPrefixes.add("fe80::/64");
+        doAnswer(invocation -> {
+            ((IOffloadControl.setLocalPrefixesCallback) invocation.getArgument(1))
+                    .onValues(expectSuccess, "");
+            return null;
+        }).when(mIOffloadControlMock).setLocalPrefixes(eq(localPrefixes), any());
+        assertEquals(expectSuccess, mIOffloadHal.setLocalPrefixes(localPrefixes));
+        verify(mIOffloadControlMock).setLocalPrefixes(eq(localPrefixes), any());
+    }
+
+    @Test
+    public void testSetLocalPrefixesSuccess() throws Exception {
+        doTestSetLocalPrefixes(true);
+    }
+
+    @Test
+    public void testSetLocalPrefixesFailure() throws Exception {
+        doTestSetLocalPrefixes(false);
+    }
+
+    private void doTestSetDataLimit(boolean expectSuccess) throws Exception {
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        final long limit = 12345;
+        doAnswer(invocation -> {
+            ((IOffloadControl.setDataLimitCallback) invocation.getArgument(2))
+                    .onValues(expectSuccess, "");
+            return null;
+        }).when(mIOffloadControlMock).setDataLimit(eq(RMNET0), eq(limit), any());
+        assertEquals(expectSuccess, mIOffloadHal.setDataLimit(RMNET0, limit));
+        verify(mIOffloadControlMock).setDataLimit(eq(RMNET0), eq(limit), any());
+    }
+
+    @Test
+    public void testSetDataLimitSuccess() throws Exception {
+        doTestSetDataLimit(true);
+    }
+
+    @Test
+    public void testSetDataLimitFailure() throws Exception {
+        doTestSetDataLimit(false);
+    }
+
+    private void doTestSetDataWarningAndLimit(boolean expectSuccess) throws Exception {
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_1);
+        final long warning = 12345;
+        final long limit = 67890;
+        doAnswer(invocation -> {
+            ((android.hardware.tetheroffload.control.V1_1.IOffloadControl
+                    .setDataWarningAndLimitCallback) invocation.getArgument(3))
+                    .onValues(expectSuccess, "");
+            return null;
+        }).when((android.hardware.tetheroffload.control.V1_1.IOffloadControl) mIOffloadControlMock)
+                .setDataWarningAndLimit(eq(RMNET0), eq(warning), eq(limit), any());
+        assertEquals(expectSuccess, mIOffloadHal.setDataWarningAndLimit(RMNET0, warning, limit));
+        verify((android.hardware.tetheroffload.control.V1_1.IOffloadControl) mIOffloadControlMock)
+                .setDataWarningAndLimit(eq(RMNET0), eq(warning), eq(limit), any());
+    }
+
+    @Test
+    public void testSetDataWarningAndLimitSuccess() throws Exception {
+        doTestSetDataWarningAndLimit(true);
+    }
+
+    @Test
+    public void testSetDataWarningAndLimitFailure() throws Exception {
+        // Verify that V1.0 control HAL would reject the function call with exception.
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        final long warning = 12345;
+        final long limit = 67890;
+        assertThrows(UnsupportedOperationException.class,
+                () -> mIOffloadHal.setDataWarningAndLimit(RMNET0, warning, limit));
+
+        doTestSetDataWarningAndLimit(false);
+    }
+
+    private void doTestSetUpstreamParameters(boolean expectSuccess) throws Exception {
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        final String v4addr = "192.168.10.1";
+        final String v4gateway = "192.168.10.255";
+        final ArrayList<String> v6gws = new ArrayList<>(0);
+        v6gws.add("2001:db8::1");
+        doAnswer(invocation -> {
+            ((IOffloadControl.setUpstreamParametersCallback) invocation.getArgument(4))
+                    .onValues(expectSuccess, "");
+            return null;
+        }).when(mIOffloadControlMock).setUpstreamParameters(eq(RMNET0), eq(v4addr), eq(v4gateway),
+                eq(v6gws), any());
+        assertEquals(expectSuccess, mIOffloadHal.setUpstreamParameters(RMNET0, v4addr, v4gateway,
+                v6gws));
+        verify(mIOffloadControlMock).setUpstreamParameters(eq(RMNET0), eq(v4addr), eq(v4gateway),
+                eq(v6gws), any());
+    }
+
+    @Test
+    public void testSetUpstreamParametersSuccess() throws Exception {
+        doTestSetUpstreamParameters(true);
+    }
+
+    @Test
+    public void testSetUpstreamParametersFailure() throws Exception {
+        doTestSetUpstreamParameters(false);
+    }
+
+    private void doTestAddDownstream(boolean expectSuccess) throws Exception {
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        final String ifName = "wlan1";
+        final String prefix = "192.168.43.0/24";
+        doAnswer(invocation -> {
+            ((IOffloadControl.addDownstreamCallback) invocation.getArgument(2))
+                    .onValues(expectSuccess, "");
+            return null;
+        }).when(mIOffloadControlMock).addDownstream(eq(ifName), eq(prefix), any());
+        assertEquals(expectSuccess, mIOffloadHal.addDownstream(ifName, prefix));
+        verify(mIOffloadControlMock).addDownstream(eq(ifName), eq(prefix), any());
+    }
+
+    @Test
+    public void testAddDownstreamSuccess() throws Exception {
+        doTestAddDownstream(true);
+    }
+
+    @Test
+    public void testAddDownstreamFailure() throws Exception {
+        doTestAddDownstream(false);
+    }
+
+    private void doTestRemoveDownstream(boolean expectSuccess) throws Exception {
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        final String ifName = "wlan1";
+        final String prefix = "192.168.43.0/24";
+        doAnswer(invocation -> {
+            ((IOffloadControl.removeDownstreamCallback) invocation.getArgument(2))
+                    .onValues(expectSuccess, "");
+            return null;
+        }).when(mIOffloadControlMock).removeDownstream(eq(ifName), eq(prefix), any());
+        assertEquals(expectSuccess, mIOffloadHal.removeDownstream(ifName, prefix));
+        verify(mIOffloadControlMock).removeDownstream(eq(ifName), eq(prefix), any());
+    }
+
+    @Test
+    public void testRemoveDownstreamSuccess() throws Exception {
+        doTestRemoveDownstream(true);
+    }
+
+    @Test
+    public void testRemoveDownstreamFailure() throws Exception {
+        doTestRemoveDownstream(false);
+    }
+
+    @Test
+    public void testTetheringOffloadCallback() throws Exception {
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_0);
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STARTED);
+        mTestLooper.dispatchAll();
+        verify(mOffloadHalCallback).onStarted();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR);
+        mTestLooper.dispatchAll();
+        verify(mOffloadHalCallback).onStoppedError();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED);
+        mTestLooper.dispatchAll();
+        verify(mOffloadHalCallback).onStoppedUnsupported();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE);
+        mTestLooper.dispatchAll();
+        verify(mOffloadHalCallback).onSupportAvailable();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED);
+        mTestLooper.dispatchAll();
+        verify(mOffloadHalCallback).onStoppedLimitReached();
+
+        final NatTimeoutUpdate tcpParams = buildNatTimeoutUpdate(NetworkProtocol.TCP);
+        mTetheringOffloadCallback.updateTimeout(tcpParams);
+        mTestLooper.dispatchAll();
+        verify(mOffloadHalCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_TCP),
+                eq(tcpParams.src.addr),
+                eq(uint16(tcpParams.src.port)),
+                eq(tcpParams.dst.addr),
+                eq(uint16(tcpParams.dst.port)));
+
+        final NatTimeoutUpdate udpParams = buildNatTimeoutUpdate(NetworkProtocol.UDP);
+        mTetheringOffloadCallback.updateTimeout(udpParams);
+        mTestLooper.dispatchAll();
+        verify(mOffloadHalCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_UDP),
+                eq(udpParams.src.addr),
+                eq(uint16(udpParams.src.port)),
+                eq(udpParams.dst.addr),
+                eq(uint16(udpParams.dst.port)));
+        reset(mOffloadHalCallback);
+
+        createAndInitOffloadHal(OFFLOAD_HAL_VERSION_HIDL_1_1);
+
+        // Verify the interface will process the events that comes from V1.1 HAL.
+        mTetheringOffloadCallback.onEvent_1_1(OffloadCallbackEvent.OFFLOAD_STARTED);
+        mTestLooper.dispatchAll();
+        final InOrder inOrder = inOrder(mOffloadHalCallback);
+        inOrder.verify(mOffloadHalCallback).onStarted();
+        inOrder.verifyNoMoreInteractions();
+
+        mTetheringOffloadCallback.onEvent_1_1(OffloadCallbackEvent.OFFLOAD_WARNING_REACHED);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mOffloadHalCallback).onWarningReached();
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    private NatTimeoutUpdate buildNatTimeoutUpdate(final int proto) {
+        final NatTimeoutUpdate params = new NatTimeoutUpdate();
+        params.proto = proto;
+        params.src.addr = "192.168.43.200";
+        params.src.port = 100;
+        params.dst.addr = "172.50.46.169";
+        params.dst.port = 150;
+        return params;
+    }
+}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
index 36b439b..b1f875b 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
@@ -20,36 +20,29 @@
 import static android.system.OsConstants.AF_UNIX;
 import static android.system.OsConstants.SOCK_STREAM;
 
-import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0;
-import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_1;
-import static com.android.networkstack.tethering.util.TetheringUtils.uint16;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_AIDL;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_0;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_1;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_NONE;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
-import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
-import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
-import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
-import android.hardware.tetheroffload.control.V1_1.ITetheringOffloadCallback;
-import android.hardware.tetheroffload.control.V1_1.OffloadCallbackEvent;
 import android.os.Handler;
 import android.os.NativeHandle;
 import android.os.test.TestLooper;
 import android.system.ErrnoException;
 import android.system.Os;
-import android.system.OsConstants;
-import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -57,12 +50,13 @@
 import com.android.net.module.util.SharedLog;
 import com.android.net.module.util.netlink.StructNfGenMsg;
 import com.android.net.module.util.netlink.StructNlMsgHdr;
+import com.android.networkstack.tethering.OffloadHardwareInterface.ForwardedStats;
+import com.android.networkstack.tethering.OffloadHardwareInterface.OffloadHalCallback;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -79,11 +73,9 @@
     private final TestLooper mTestLooper = new TestLooper();
 
     private OffloadHardwareInterface mOffloadHw;
-    private ITetheringOffloadCallback mTetheringOffloadCallback;
-    private OffloadHardwareInterface.ControlCallback mControlCallback;
+    private OffloadHalCallback mOffloadHalCallback;
 
-    @Mock private IOffloadConfig mIOffloadConfig;
-    private IOffloadControl mIOffloadControl;
+    @Mock private IOffloadHal mIOffload;
     @Mock private NativeHandle mNativeHandle;
 
     // Random values to test Netlink message.
@@ -91,32 +83,16 @@
     private static final short TEST_FLAGS = 263;
 
     class MyDependencies extends OffloadHardwareInterface.Dependencies {
-        private final int mMockControlVersion;
-        MyDependencies(SharedLog log, final int mockControlVersion) {
-            super(log);
-            mMockControlVersion = mockControlVersion;
+        private final int mMockOffloadHalVersion;
+        MyDependencies(Handler handler, SharedLog log, final int mockOffloadHalVersion) {
+            super(handler, log);
+            mMockOffloadHalVersion = mockOffloadHalVersion;
+            when(mIOffload.getVersion()).thenReturn(mMockOffloadHalVersion);
         }
 
         @Override
-        public IOffloadConfig getOffloadConfig() {
-            return mIOffloadConfig;
-        }
-
-        @Override
-        public Pair<IOffloadControl, Integer> getOffloadControl() {
-            switch (mMockControlVersion) {
-                case OFFLOAD_HAL_VERSION_1_0:
-                    mIOffloadControl = mock(IOffloadControl.class);
-                    break;
-                case OFFLOAD_HAL_VERSION_1_1:
-                    mIOffloadControl =
-                            mock(android.hardware.tetheroffload.control.V1_1.IOffloadControl.class);
-                    break;
-                default:
-                    throw new IllegalArgumentException("Invalid offload control version "
-                            + mMockControlVersion);
-            }
-            return new Pair<IOffloadControl, Integer>(mIOffloadControl, mMockControlVersion);
+        public IOffloadHal getOffload() {
+            return mMockOffloadHalVersion == OFFLOAD_HAL_VERSION_NONE ? null : mIOffload;
         }
 
         @Override
@@ -128,156 +104,140 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mControlCallback = spy(new OffloadHardwareInterface.ControlCallback());
+        mOffloadHalCallback = new OffloadHalCallback();
+        when(mIOffload.initOffload(any(NativeHandle.class), any(NativeHandle.class),
+                any(OffloadHalCallback.class))).thenReturn(true);
     }
 
-    private void startOffloadHardwareInterface(int controlVersion) throws Exception {
+    private void startOffloadHardwareInterface(int offloadHalVersion)
+            throws Exception {
         final SharedLog log = new SharedLog("test");
-        mOffloadHw = new OffloadHardwareInterface(new Handler(mTestLooper.getLooper()), log,
-                new MyDependencies(log, controlVersion));
-        mOffloadHw.initOffloadConfig();
-        mOffloadHw.initOffloadControl(mControlCallback);
-        final ArgumentCaptor<ITetheringOffloadCallback> mOffloadCallbackCaptor =
-                ArgumentCaptor.forClass(ITetheringOffloadCallback.class);
-        verify(mIOffloadControl).initOffload(mOffloadCallbackCaptor.capture(), any());
-        mTetheringOffloadCallback = mOffloadCallbackCaptor.getValue();
+        final Handler handler = new Handler(mTestLooper.getLooper());
+        final int num = offloadHalVersion != OFFLOAD_HAL_VERSION_NONE ? 1 : 0;
+        mOffloadHw = new OffloadHardwareInterface(handler, log,
+                new MyDependencies(handler, log, offloadHalVersion));
+        assertEquals(offloadHalVersion, mOffloadHw.initOffload(mOffloadHalCallback));
+        verify(mIOffload, times(num)).initOffload(any(NativeHandle.class), any(NativeHandle.class),
+                eq(mOffloadHalCallback));
+    }
+
+    @Test
+    public void testInitFailureWithNoHal() throws Exception {
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_NONE);
+    }
+
+    @Test
+    public void testInitSuccessWithAidl() throws Exception {
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_AIDL);
+    }
+
+    @Test
+    public void testInitSuccessWithHidl_1_0() throws Exception {
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0);
+    }
+
+    @Test
+    public void testInitSuccessWithHidl_1_1() throws Exception {
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_1);
     }
 
     @Test
     public void testGetForwardedStats() throws Exception {
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
-        final OffloadHardwareInterface.ForwardedStats stats = mOffloadHw.getForwardedStats(RMNET0);
-        verify(mIOffloadControl).getForwardedStats(eq(RMNET0), any());
-        assertNotNull(stats);
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        ForwardedStats stats = new ForwardedStats(12345, 56780);
+        when(mIOffload.getForwardedStats(anyString())).thenReturn(stats);
+        assertEquals(mOffloadHw.getForwardedStats(RMNET0), stats);
+        verify(mIOffload).getForwardedStats(eq(RMNET0));
     }
 
     @Test
     public void testSetLocalPrefixes() throws Exception {
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0);
         final ArrayList<String> localPrefixes = new ArrayList<>();
         localPrefixes.add("127.0.0.0/8");
         localPrefixes.add("fe80::/64");
-        mOffloadHw.setLocalPrefixes(localPrefixes);
-        verify(mIOffloadControl).setLocalPrefixes(eq(localPrefixes), any());
+        when(mIOffload.setLocalPrefixes(any())).thenReturn(true);
+        assertTrue(mOffloadHw.setLocalPrefixes(localPrefixes));
+        verify(mIOffload).setLocalPrefixes(eq(localPrefixes));
+        when(mIOffload.setLocalPrefixes(any())).thenReturn(false);
+        assertFalse(mOffloadHw.setLocalPrefixes(localPrefixes));
     }
 
     @Test
     public void testSetDataLimit() throws Exception {
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0);
         final long limit = 12345;
-        mOffloadHw.setDataLimit(RMNET0, limit);
-        verify(mIOffloadControl).setDataLimit(eq(RMNET0), eq(limit), any());
+        when(mIOffload.setDataLimit(anyString(), anyLong())).thenReturn(true);
+        assertTrue(mOffloadHw.setDataLimit(RMNET0, limit));
+        verify(mIOffload).setDataLimit(eq(RMNET0), eq(limit));
+        when(mIOffload.setDataLimit(anyString(), anyLong())).thenReturn(false);
+        assertFalse(mOffloadHw.setDataLimit(RMNET0, limit));
+    }
+
+    @Test
+    public void testSetDataWarningAndLimitFailureWithHidl_1_0() throws Exception {
+        // Verify V1.0 control HAL would reject the function call with exception.
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0);
+        final long warning = 12345;
+        final long limit = 67890;
+        assertThrows(UnsupportedOperationException.class,
+                () -> mOffloadHw.setDataWarningAndLimit(RMNET0, warning, limit));
     }
 
     @Test
     public void testSetDataWarningAndLimit() throws Exception {
-        // Verify V1.0 control HAL would reject the function call with exception.
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
+        // Verify V1.1 control HAL could receive this function call.
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_1);
         final long warning = 12345;
         final long limit = 67890;
-        assertThrows(IllegalArgumentException.class,
-                () -> mOffloadHw.setDataWarningAndLimit(RMNET0, warning, limit));
-        reset(mIOffloadControl);
-
-        // Verify V1.1 control HAL could receive this function call.
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_1);
-        mOffloadHw.setDataWarningAndLimit(RMNET0, warning, limit);
-        verify((android.hardware.tetheroffload.control.V1_1.IOffloadControl) mIOffloadControl)
-                .setDataWarningAndLimit(eq(RMNET0), eq(warning), eq(limit), any());
+        when(mIOffload.setDataWarningAndLimit(anyString(), anyLong(), anyLong())).thenReturn(true);
+        assertTrue(mOffloadHw.setDataWarningAndLimit(RMNET0, warning, limit));
+        verify(mIOffload).setDataWarningAndLimit(eq(RMNET0), eq(warning), eq(limit));
+        when(mIOffload.setDataWarningAndLimit(anyString(), anyLong(), anyLong())).thenReturn(false);
+        assertFalse(mOffloadHw.setDataWarningAndLimit(RMNET0, warning, limit));
     }
 
     @Test
     public void testSetUpstreamParameters() throws Exception {
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0);
         final String v4addr = "192.168.10.1";
         final String v4gateway = "192.168.10.255";
         final ArrayList<String> v6gws = new ArrayList<>(0);
         v6gws.add("2001:db8::1");
-        mOffloadHw.setUpstreamParameters(RMNET0, v4addr, v4gateway, v6gws);
-        verify(mIOffloadControl).setUpstreamParameters(eq(RMNET0), eq(v4addr), eq(v4gateway),
-                eq(v6gws), any());
+        when(mIOffload.setUpstreamParameters(anyString(), anyString(), anyString(), any()))
+                .thenReturn(true);
+        assertTrue(mOffloadHw.setUpstreamParameters(RMNET0, v4addr, v4gateway, v6gws));
+        verify(mIOffload).setUpstreamParameters(eq(RMNET0), eq(v4addr), eq(v4gateway), eq(v6gws));
 
         final ArgumentCaptor<ArrayList<String>> mArrayListCaptor =
                 ArgumentCaptor.forClass(ArrayList.class);
-        mOffloadHw.setUpstreamParameters(null, null, null, null);
-        verify(mIOffloadControl).setUpstreamParameters(eq(""), eq(""), eq(""),
-                mArrayListCaptor.capture(), any());
+        when(mIOffload.setUpstreamParameters(anyString(), anyString(), anyString(), any()))
+                .thenReturn(false);
+        assertFalse(mOffloadHw.setUpstreamParameters(null, null, null, null));
+        verify(mIOffload).setUpstreamParameters(eq(""), eq(""), eq(""), mArrayListCaptor.capture());
         assertEquals(mArrayListCaptor.getValue().size(), 0);
     }
 
     @Test
-    public void testUpdateDownstreamPrefix() throws Exception {
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
+    public void testUpdateDownstream() throws Exception {
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0);
         final String ifName = "wlan1";
         final String prefix = "192.168.43.0/24";
-        mOffloadHw.addDownstreamPrefix(ifName, prefix);
-        verify(mIOffloadControl).addDownstream(eq(ifName), eq(prefix), any());
-
-        mOffloadHw.removeDownstreamPrefix(ifName, prefix);
-        verify(mIOffloadControl).removeDownstream(eq(ifName), eq(prefix), any());
-    }
-
-    @Test
-    public void testTetheringOffloadCallback() throws Exception {
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
-
-        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STARTED);
-        mTestLooper.dispatchAll();
-        verify(mControlCallback).onStarted();
-
-        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR);
-        mTestLooper.dispatchAll();
-        verify(mControlCallback).onStoppedError();
-
-        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED);
-        mTestLooper.dispatchAll();
-        verify(mControlCallback).onStoppedUnsupported();
-
-        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE);
-        mTestLooper.dispatchAll();
-        verify(mControlCallback).onSupportAvailable();
-
-        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED);
-        mTestLooper.dispatchAll();
-        verify(mControlCallback).onStoppedLimitReached();
-
-        final NatTimeoutUpdate tcpParams = buildNatTimeoutUpdate(NetworkProtocol.TCP);
-        mTetheringOffloadCallback.updateTimeout(tcpParams);
-        mTestLooper.dispatchAll();
-        verify(mControlCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_TCP),
-                eq(tcpParams.src.addr),
-                eq(uint16(tcpParams.src.port)),
-                eq(tcpParams.dst.addr),
-                eq(uint16(tcpParams.dst.port)));
-
-        final NatTimeoutUpdate udpParams = buildNatTimeoutUpdate(NetworkProtocol.UDP);
-        mTetheringOffloadCallback.updateTimeout(udpParams);
-        mTestLooper.dispatchAll();
-        verify(mControlCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_UDP),
-                eq(udpParams.src.addr),
-                eq(uint16(udpParams.src.port)),
-                eq(udpParams.dst.addr),
-                eq(uint16(udpParams.dst.port)));
-        reset(mControlCallback);
-
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_1);
-
-        // Verify the interface will process the events that comes from V1.1 HAL.
-        mTetheringOffloadCallback.onEvent_1_1(OffloadCallbackEvent.OFFLOAD_STARTED);
-        mTestLooper.dispatchAll();
-        final InOrder inOrder = inOrder(mControlCallback);
-        inOrder.verify(mControlCallback).onStarted();
-        inOrder.verifyNoMoreInteractions();
-
-        mTetheringOffloadCallback.onEvent_1_1(OffloadCallbackEvent.OFFLOAD_WARNING_REACHED);
-        mTestLooper.dispatchAll();
-        inOrder.verify(mControlCallback).onWarningReached();
-        inOrder.verifyNoMoreInteractions();
+        when(mIOffload.addDownstream(anyString(), anyString())).thenReturn(true);
+        assertTrue(mOffloadHw.addDownstream(ifName, prefix));
+        verify(mIOffload).addDownstream(eq(ifName), eq(prefix));
+        when(mIOffload.addDownstream(anyString(), anyString())).thenReturn(false);
+        assertFalse(mOffloadHw.addDownstream(ifName, prefix));
+        when(mIOffload.removeDownstream(anyString(), anyString())).thenReturn(true);
+        assertTrue(mOffloadHw.removeDownstream(ifName, prefix));
+        verify(mIOffload).removeDownstream(eq(ifName), eq(prefix));
+        when(mIOffload.removeDownstream(anyString(), anyString())).thenReturn(false);
+        assertFalse(mOffloadHw.removeDownstream(ifName, prefix));
     }
 
     @Test
     public void testSendIpv4NfGenMsg() throws Exception {
-        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0);
+        startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0);
         FileDescriptor writeSocket = new FileDescriptor();
         FileDescriptor readSocket = new FileDescriptor();
         try {
@@ -308,14 +268,4 @@
         assertEquals(0 /* error */, buffer.getShort());  // res_id
         assertEquals(expectedLen, buffer.position());
     }
-
-    private NatTimeoutUpdate buildNatTimeoutUpdate(final int proto) {
-        final NatTimeoutUpdate params = new NatTimeoutUpdate();
-        params.proto = proto;
-        params.src.addr = "192.168.43.200";
-        params.src.port = 100;
-        params.dst.addr = "172.50.46.169";
-        params.dst.port = 150;
-        return params;
-    }
 }
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
index 55d9852..91b092a 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
@@ -19,6 +19,8 @@
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
 import static android.net.TetheringManager.TETHERING_ETHERNET;
 import static android.net.TetheringManager.TETHERING_USB;
 import static android.net.TetheringManager.TETHERING_WIFI;
@@ -61,6 +63,7 @@
     private static final String TEST_IFNAME = "test0";
 
     @Mock private IpServer mHotspotIpServer;
+    @Mock private IpServer mLocalHotspotIpServer;
     @Mock private IpServer mUsbIpServer;
     @Mock private IpServer mEthernetIpServer;
     @Mock private IpServer mWifiP2pIpServer;
@@ -90,6 +93,7 @@
         when(mUsbIpServer.interfaceType()).thenReturn(TETHERING_USB);
         when(mEthernetIpServer.interfaceType()).thenReturn(TETHERING_ETHERNET);
         when(mHotspotIpServer.interfaceType()).thenReturn(TETHERING_WIFI);
+        when(mLocalHotspotIpServer.interfaceType()).thenReturn(TETHERING_WIFI);
         when(mWifiP2pIpServer.interfaceType()).thenReturn(TETHERING_WIFI_P2P);
     }
 
@@ -104,9 +108,10 @@
         mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(mContext, mConfig));
     }
 
-    private LinkAddress requestDownstreamAddress(final IpServer ipServer, boolean useLastAddress) {
+    private LinkAddress requestDownstreamAddress(final IpServer ipServer, int scope,
+            boolean useLastAddress) {
         final LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
-                ipServer, useLastAddress);
+                ipServer, scope, useLastAddress);
         when(ipServer.getAddress()).thenReturn(address);
         return address;
     }
@@ -115,19 +120,19 @@
     public void testRequestDownstreamAddressWithoutUsingLastAddress() throws Exception {
         final IpPrefix bluetoothPrefix = asIpPrefix(mBluetoothAddress);
         final LinkAddress address = requestDownstreamAddress(mHotspotIpServer,
-                false /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
         final IpPrefix hotspotPrefix = asIpPrefix(address);
         assertNotEquals(hotspotPrefix, bluetoothPrefix);
 
         final LinkAddress newAddress = requestDownstreamAddress(mHotspotIpServer,
-                false /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
         final IpPrefix testDupRequest = asIpPrefix(newAddress);
         assertNotEquals(hotspotPrefix, testDupRequest);
         assertNotEquals(bluetoothPrefix, testDupRequest);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
 
         final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer,
-                false /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
         final IpPrefix usbPrefix = asIpPrefix(usbAddress);
         assertNotEquals(usbPrefix, bluetoothPrefix);
         assertNotEquals(usbPrefix, hotspotPrefix);
@@ -139,25 +144,28 @@
         int fakeSubAddr = 0x2b00; // 43.0.
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
         LinkAddress actualAddress = requestDownstreamAddress(mHotspotIpServer,
-                false /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
         assertEquals(new LinkAddress("192.168.43.2/24"), actualAddress);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
 
         fakeSubAddr = 0x2d01; // 45.1.
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
-        actualAddress = requestDownstreamAddress(mHotspotIpServer, false /* useLastAddress */);
+        actualAddress = requestDownstreamAddress(mHotspotIpServer,
+                CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
         assertEquals(new LinkAddress("192.168.45.2/24"), actualAddress);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
 
         fakeSubAddr = 0x2eff; // 46.255.
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
-        actualAddress = requestDownstreamAddress(mHotspotIpServer, false /* useLastAddress */);
+        actualAddress = requestDownstreamAddress(mHotspotIpServer,
+                CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
         assertEquals(new LinkAddress("192.168.46.254/24"), actualAddress);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
 
         fakeSubAddr = 0x2f05; // 47.5.
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
-        actualAddress = requestDownstreamAddress(mHotspotIpServer, false /* useLastAddress */);
+        actualAddress = requestDownstreamAddress(mHotspotIpServer,
+                CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
         assertEquals(new LinkAddress("192.168.47.5/24"), actualAddress);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
     }
@@ -168,7 +176,7 @@
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
                 getSubAddress(mBluetoothAddress.getAddress().getAddress()));
         final LinkAddress hotspotAddress = requestDownstreamAddress(mHotspotIpServer,
-                false /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
         final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddress);
         assertNotEquals(asIpPrefix(mBluetoothAddress), hotspotPrefix);
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
@@ -177,7 +185,7 @@
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
                 getSubAddress(hotspotAddress.getAddress().getAddress()));
         final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer,
-                false /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
         final IpPrefix usbPrefix = asIpPrefix(usbAddress);
         assertNotEquals(asIpPrefix(mBluetoothAddress), usbPrefix);
         assertNotEquals(hotspotPrefix, usbPrefix);
@@ -187,7 +195,7 @@
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
                 getSubAddress(mLegacyWifiP2pAddress.getAddress().getAddress()));
         final LinkAddress etherAddress = requestDownstreamAddress(mEthernetIpServer,
-                false /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
         final IpPrefix etherPrefix = asIpPrefix(etherAddress);
         assertNotEquals(asIpPrefix(mLegacyWifiP2pAddress), etherPrefix);
         assertNotEquals(asIpPrefix(mBluetoothAddress), etherPrefix);
@@ -200,11 +208,11 @@
         final int fakeHotspotSubAddr = 0x2b05; // 43.5
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
         final LinkAddress hotspotAddress = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong wifi prefix: ", new LinkAddress("192.168.43.5/24"), hotspotAddress);
 
         final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong wifi prefix: ", new LinkAddress("192.168.45.5/24"), usbAddress);
 
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
@@ -214,10 +222,10 @@
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
 
         final LinkAddress newHotspotAddress = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals(hotspotAddress, newHotspotAddress);
         final LinkAddress newUsbAddress = requestDownstreamAddress(mUsbIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals(usbAddress, newUsbAddress);
 
         final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
@@ -257,7 +265,7 @@
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
         // - Enable hotspot with prefix 192.168.43.0/24
         final LinkAddress hotspotAddr = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddr);
         assertEquals("Wrong wifi prefix: ", predefinedPrefix, hotspotPrefix);
         // - test mobile network with null NetworkCapabilities. Ideally this should not happen
@@ -311,21 +319,21 @@
         // - Restart hotspot again and its prefix is different previous.
         mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
         final LinkAddress hotspotAddr2 = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         final IpPrefix hotspotPrefix2 = asIpPrefix(hotspotAddr2);
         assertNotEquals(hotspotPrefix, hotspotPrefix2);
         mPrivateAddressCoordinator.updateUpstreamPrefix(v4OnlyWifi);
         verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
         // - Usb tethering can be enabled and its prefix is different with conflict one.
         final LinkAddress usbAddr = requestDownstreamAddress(mUsbIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         final IpPrefix usbPrefix = asIpPrefix(usbAddr);
         assertNotEquals(predefinedPrefix, usbPrefix);
         assertNotEquals(hotspotPrefix2, usbPrefix);
         // - Disable wifi upstream, then wifi's prefix can be selected again.
         mPrivateAddressCoordinator.removeUpstreamPrefix(mWifiNetwork);
         final LinkAddress ethAddr = requestDownstreamAddress(mEthernetIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         final IpPrefix ethPrefix = asIpPrefix(ethAddr);
         assertEquals(predefinedPrefix, ethPrefix);
     }
@@ -335,7 +343,7 @@
         final int randomAddress = 0x8605; // 134.5
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomAddress);
         final LinkAddress addr0 = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         // Check whether return address is prefix 192.168.0.0/16 + subAddress 0.0.134.5.
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.134.5/24"), addr0);
         final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
@@ -345,7 +353,7 @@
 
         // Check whether return address is next prefix of 192.168.134.0/24.
         final LinkAddress addr1 = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.135.5/24"), addr1);
         final UpstreamNetworkState wifiUpstream2 = buildUpstreamNetworkState(mWifiNetwork,
                 new LinkAddress("192.168.149.16/19"), null,
@@ -355,7 +363,7 @@
 
         // The conflict range is 128 ~ 159, so the address is 192.168.160.5/24.
         final LinkAddress addr2 = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.160.5/24"), addr2);
         final UpstreamNetworkState mobileUpstream = buildUpstreamNetworkState(mMobileNetwork,
                 new LinkAddress("192.168.129.53/18"), null,
@@ -370,7 +378,7 @@
 
         // The conflict range are 128 ~ 159 and 159 ~ 191, so the address is 192.168.192.5/24.
         final LinkAddress addr3 = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.192.5/24"), addr3);
         final UpstreamNetworkState mobileUpstream3 = buildUpstreamNetworkState(mMobileNetwork3,
                 new LinkAddress("192.168.188.133/17"), null,
@@ -380,7 +388,7 @@
         // Conflict range: 128 ~ 255. The next available address is 192.168.0.5 because
         // 192.168.134/24 ~ 192.168.255.255/24 is not available.
         final LinkAddress addr4 = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.0.5/24"), addr4);
         final UpstreamNetworkState mobileUpstream4 = buildUpstreamNetworkState(mMobileNetwork4,
                 new LinkAddress("192.168.3.59/21"), null,
@@ -389,7 +397,7 @@
 
         // Conflict ranges: 128 ~ 255 and 0 ~ 7, so the address is 192.168.8.5/24.
         final LinkAddress addr5 = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.8.5/24"), addr5);
         final UpstreamNetworkState mobileUpstream5 = buildUpstreamNetworkState(mMobileNetwork5,
                 new LinkAddress("192.168.68.43/21"), null,
@@ -399,7 +407,7 @@
         // Update an upstream that does *not* conflict, check whether return the same address
         // 192.168.5/24.
         final LinkAddress addr6 = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.8.5/24"), addr6);
         final UpstreamNetworkState mobileUpstream6 = buildUpstreamNetworkState(mMobileNetwork6,
                 new LinkAddress("192.168.10.97/21"), null,
@@ -408,7 +416,7 @@
 
         // Conflict ranges: 0 ~ 15 and 128 ~ 255, so the address is 192.168.16.5/24.
         final LinkAddress addr7 = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.16.5/24"), addr7);
         final UpstreamNetworkState mobileUpstream7 = buildUpstreamNetworkState(mMobileNetwork6,
                 new LinkAddress("192.168.0.0/17"), null,
@@ -417,7 +425,7 @@
 
         // Choose prefix from next range(172.16.0.0/12) when no available prefix in 192.168.0.0/16.
         final LinkAddress addr8 = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("172.16.134.5/24"), addr8);
     }
 
@@ -426,7 +434,7 @@
         final int randomAddress = 0x1f2b2a; // 31.43.42
         when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomAddress);
         final LinkAddress classC1 = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         // Check whether return address is prefix 192.168.0.0/16 + subAddress 0.0.43.42.
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.43.42/24"), classC1);
         final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
@@ -437,7 +445,7 @@
 
         // Check whether return address is next address of prefix 192.168.128.0/17.
         final LinkAddress classC2 = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("192.168.128.42/24"), classC2);
         final UpstreamNetworkState mobileUpstream = buildUpstreamNetworkState(mMobileNetwork,
                 new LinkAddress("192.1.2.3/8"), null,
@@ -447,7 +455,7 @@
 
         // Check whether return address is under prefix 172.16.0.0/12.
         final LinkAddress classB1 = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("172.31.43.42/24"), classB1);
         final UpstreamNetworkState mobileUpstream2 = buildUpstreamNetworkState(mMobileNetwork2,
                 new LinkAddress("172.28.123.100/14"), null,
@@ -458,12 +466,12 @@
         // 172.28.0.0 ~ 172.31.255.255 is not available.
         // Check whether return address is next address of prefix 172.16.0.0/14.
         final LinkAddress classB2 = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("172.16.0.42/24"), classB2);
 
         // Check whether new downstream is next address of address 172.16.0.42/24.
         final LinkAddress classB3 = requestDownstreamAddress(mUsbIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("172.16.1.42/24"), classB3);
         final UpstreamNetworkState mobileUpstream3 = buildUpstreamNetworkState(mMobileNetwork3,
                 new LinkAddress("172.16.0.1/24"), null,
@@ -474,7 +482,7 @@
 
         // Check whether return address is next address of prefix 172.16.1.42/24.
         final LinkAddress classB4 = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("172.16.2.42/24"), classB4);
         final UpstreamNetworkState mobileUpstream4 = buildUpstreamNetworkState(mMobileNetwork4,
                 new LinkAddress("172.16.0.1/13"), null,
@@ -485,11 +493,11 @@
 
         // Check whether return address is next address of prefix 172.16.0.1/13.
         final LinkAddress classB5 = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("172.24.0.42/24"), classB5);
         // Check whether return address is next address of prefix 172.24.0.42/24.
         final LinkAddress classB6 = requestDownstreamAddress(mUsbIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("172.24.1.42/24"), classB6);
         final UpstreamNetworkState mobileUpstream5 = buildUpstreamNetworkState(mMobileNetwork5,
                 new LinkAddress("172.24.0.1/12"), null,
@@ -500,11 +508,11 @@
 
         // Check whether return address is prefix 10.0.0.0/8 + subAddress 0.31.43.42.
         final LinkAddress classA1 = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("10.31.43.42/24"), classA1);
         // Check whether new downstream is next address of address 10.31.43.42/24.
         final LinkAddress classA2 = requestDownstreamAddress(mUsbIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         assertEquals("Wrong prefix: ", new LinkAddress("10.31.44.42/24"), classA2);
     }
 
@@ -524,7 +532,7 @@
 
     private void assertReseveredWifiP2pPrefix() throws Exception {
         LinkAddress address = requestDownstreamAddress(mHotspotIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
         final IpPrefix hotspotPrefix = asIpPrefix(address);
         final IpPrefix legacyWifiP2pPrefix = asIpPrefix(mLegacyWifiP2pAddress);
         assertNotEquals(legacyWifiP2pPrefix, hotspotPrefix);
@@ -544,8 +552,23 @@
 
         // If #shouldEnableWifiP2pDedicatedIp() is enabled, wifi P2P gets the configured address.
         LinkAddress address = requestDownstreamAddress(mWifiP2pIpServer,
-                true /* useLastAddress */);
+                CONNECTIVITY_SCOPE_LOCAL, true /* useLastAddress */);
         assertEquals(mLegacyWifiP2pAddress, address);
         mPrivateAddressCoordinator.releaseDownstream(mWifiP2pIpServer);
     }
+
+    @Test
+    public void testEnableSapAndLohsConcurrently() throws Exception {
+        // 0x2b05 -> 43.5, 0x8605 -> 134.5
+        when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(0x2b05, 0x8605);
+
+        final LinkAddress hotspotAddress = requestDownstreamAddress(mHotspotIpServer,
+                CONNECTIVITY_SCOPE_GLOBAL, true /* useLastAddress */);
+        assertEquals("Wrong hotspot prefix: ", new LinkAddress("192.168.43.5/24"), hotspotAddress);
+
+        final LinkAddress localHotspotAddress = requestDownstreamAddress(mLocalHotspotIpServer,
+                CONNECTIVITY_SCOPE_LOCAL, true /* useLastAddress */);
+        assertEquals("Wrong local hotspot prefix: ", new LinkAddress("192.168.134.5/24"),
+                localHotspotAddress);
+    }
 }
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
index f662c02..3382af8 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -21,14 +21,13 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
 import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 import static android.telephony.CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL;
 import static android.telephony.CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL;
+import static com.android.networkstack.tethering.TetheringConfiguration.OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD;
+import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER;
 import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_FORCE_USB_FUNCTIONS;
 import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_USB_NCM_FUNCTION;
 import static com.android.networkstack.tethering.TetheringConfiguration.TETHER_USB_RNDIS_FUNCTION;
@@ -39,6 +38,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -49,12 +49,13 @@
 import android.content.res.Resources;
 import android.os.Build;
 import android.os.PersistableBundle;
-import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
 import android.telephony.TelephonyManager;
 import android.test.mock.MockContentResolver;
+import android.util.ArrayMap;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -73,8 +74,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
+import org.mockito.MockitoAnnotations;
 
 import java.util.Arrays;
 import java.util.Iterator;
@@ -102,13 +102,13 @@
     @Mock private ModuleInfo mMi;
     private Context mMockContext;
     private boolean mHasTelephonyManager;
-    private MockitoSession mMockingSession;
     private MockContentResolver mContentResolver;
     private final PersistableBundle mCarrierConfig = new PersistableBundle();
+    private final MockDependencies mDeps = new MockDependencies();
 
     private class MockTetheringConfiguration extends TetheringConfiguration {
         MockTetheringConfiguration(Context ctx, SharedLog log, int id) {
-            super(ctx, log, id);
+            super(ctx, log, id, mDeps);
         }
 
         @Override
@@ -151,19 +151,43 @@
         }
     }
 
+    private static class MockDependencies extends TetheringConfiguration.Dependencies {
+        private ArrayMap<String, Boolean> mMockFlags = new ArrayMap<>();
+
+        @Override
+        boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
+                @NonNull String name, @NonNull String moduleName, boolean defaultEnabled) {
+            return isMockFlagEnabled(name, defaultEnabled);
+        }
+
+        @Override
+        boolean getDeviceConfigBoolean(@NonNull String namespace, @NonNull String name,
+                boolean defaultValue) {
+            // Flags should use isFeatureEnabled instead of getBoolean; see comments in
+            // DeviceConfigUtils. getBoolean should only be used for the two legacy flags below.
+            assertTrue(OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD.equals(name)
+                    || TETHER_ENABLE_LEGACY_DHCP_SERVER.equals(name));
+
+            // Use the same mocking strategy as isFeatureEnabled for testing
+            return isMockFlagEnabled(name, defaultValue);
+        }
+
+        private boolean isMockFlagEnabled(@NonNull String name, boolean defaultEnabled) {
+            final Boolean flag = mMockFlags.getOrDefault(name, defaultEnabled);
+            // Value in the map can also be null
+            if (flag != null) return flag;
+            return defaultEnabled;
+        }
+
+        void setFeatureEnabled(@NonNull String flag, Boolean enabled) {
+            mMockFlags.put(flag, enabled);
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
-        // TODO: use a dependencies class instead of mock statics.
-        mMockingSession = mockitoSession()
-                .initMocks(this)
-                .mockStatic(DeviceConfig.class)
-                .strictness(Strictness.WARN)
-                .startMocking();
-        DeviceConfigUtils.resetPackageVersionCacheForTest();
-        doReturn(null).when(
-                () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
-                eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER)));
-        setTetherForceUpstreamAutomaticFlagVersion(null);
+        MockitoAnnotations.initMocks(this);
+        setTetherForceUpstreamAutomaticFlagEnabled(null);
 
         final PackageInfo pi = new PackageInfo();
         pi.setLongVersionCode(TEST_PACKAGE_VERSION);
@@ -202,7 +226,6 @@
 
     @After
     public void tearDown() throws Exception {
-        mMockingSession.finishMocking();
         DeviceConfigUtils.resetPackageVersionCacheForTest();
         // Call {@link #clearSettingsProvider()} before and after using FakeSettingsProvider.
         FakeSettingsProvider.clearSettingsProvider();
@@ -211,7 +234,7 @@
     private TetheringConfiguration getTetheringConfiguration(int... legacyTetherUpstreamTypes) {
         when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
                 legacyTetherUpstreamTypes);
-        return new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+        return new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
     }
 
     @Test
@@ -297,7 +320,7 @@
         when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
 
         final TetheringConfiguration cfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator();
         assertTrue(upstreamIterator.hasNext());
         assertEquals(TYPE_ETHERNET, upstreamIterator.next().intValue());
@@ -320,7 +343,7 @@
         when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
 
         final TetheringConfiguration cfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator();
         assertTrue(upstreamIterator.hasNext());
         assertEquals(TYPE_ETHERNET, upstreamIterator.next().intValue());
@@ -338,7 +361,7 @@
         when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
 
         final TetheringConfiguration cfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator();
         assertTrue(upstreamIterator.hasNext());
         assertEquals(TYPE_WIFI, upstreamIterator.next().intValue());
@@ -350,27 +373,26 @@
     }
 
     private void initializeBpfOffloadConfiguration(
-            final boolean fromRes, final String fromDevConfig) {
+            final boolean fromRes, final Boolean fromDevConfig) {
         when(mResources.getBoolean(R.bool.config_tether_enable_bpf_offload)).thenReturn(fromRes);
-        doReturn(fromDevConfig).when(
-                () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
-                eq(TetheringConfiguration.OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD)));
+        mDeps.setFeatureEnabled(
+                TetheringConfiguration.OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD, fromDevConfig);
     }
 
     @Test
     public void testBpfOffloadEnabledByResource() {
         initializeBpfOffloadConfiguration(true, null /* unset */);
         final TetheringConfiguration enableByRes =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertTrue(enableByRes.isBpfOffloadEnabled());
     }
 
     @Test
     public void testBpfOffloadEnabledByDeviceConfigOverride() {
         for (boolean res : new boolean[]{true, false}) {
-            initializeBpfOffloadConfiguration(res, "true");
+            initializeBpfOffloadConfiguration(res, true);
             final TetheringConfiguration enableByDevConOverride =
-                    new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                    new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
             assertTrue(enableByDevConOverride.isBpfOffloadEnabled());
         }
     }
@@ -379,16 +401,16 @@
     public void testBpfOffloadDisabledByResource() {
         initializeBpfOffloadConfiguration(false, null /* unset */);
         final TetheringConfiguration disableByRes =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertFalse(disableByRes.isBpfOffloadEnabled());
     }
 
     @Test
     public void testBpfOffloadDisabledByDeviceConfigOverride() {
         for (boolean res : new boolean[]{true, false}) {
-            initializeBpfOffloadConfiguration(res, "false");
+            initializeBpfOffloadConfiguration(res, false);
             final TetheringConfiguration disableByDevConOverride =
-                    new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                    new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
             assertFalse(disableByDevConOverride.isBpfOffloadEnabled());
         }
     }
@@ -397,22 +419,18 @@
     public void testNewDhcpServerDisabled() {
         when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
                 true);
-        doReturn("false").when(
-                () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
-                eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER)));
+        mDeps.setFeatureEnabled(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER, false);
 
         final TetheringConfiguration enableByRes =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertTrue(enableByRes.useLegacyDhcpServer());
 
         when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
                 false);
-        doReturn("true").when(
-                () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
-                eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER)));
+        mDeps.setFeatureEnabled(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER, true);
 
         final TetheringConfiguration enableByDevConfig =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertTrue(enableByDevConfig.useLegacyDhcpServer());
     }
 
@@ -420,12 +438,10 @@
     public void testNewDhcpServerEnabled() {
         when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
                 false);
-        doReturn("false").when(
-                () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
-                eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER)));
+        mDeps.setFeatureEnabled(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER, false);
 
         final TetheringConfiguration cfg =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
 
         assertFalse(cfg.useLegacyDhcpServer());
     }
@@ -433,7 +449,7 @@
     @Test
     public void testOffloadIntervalByResource() {
         final TetheringConfiguration intervalByDefault =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertEquals(TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS,
                 intervalByDefault.getOffloadPollInterval());
 
@@ -442,7 +458,7 @@
             when(mResources.getInteger(R.integer.config_tether_offload_poll_interval)).thenReturn(
                     override);
             final TetheringConfiguration overrideByRes =
-                    new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                    new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
             assertEquals(override, overrideByRes.getOffloadPollInterval());
         }
     }
@@ -451,7 +467,7 @@
     public void testGetResourcesBySubId() {
         setUpResourceForSubId();
         final TetheringConfiguration cfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertTrue(cfg.provisioningApp.length == 0);
         final int anyValidSubId = 1;
         final MockTetheringConfiguration mockCfg =
@@ -493,7 +509,7 @@
         mockService(Context.CARRIER_CONFIG_SERVICE,
                 CarrierConfigManager.class, null);
         final TetheringConfiguration cfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
 
         assertTrue(cfg.isCarrierSupportTethering);
         assertTrue(cfg.isCarrierConfigAffirmsEntitlementCheckRequired);
@@ -506,7 +522,7 @@
                 CarrierConfigManager.class, mCarrierConfigManager);
         when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(null);
         final TetheringConfiguration cfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
 
         assertTrue(cfg.isCarrierSupportTethering);
         assertTrue(cfg.isCarrierConfigAffirmsEntitlementCheckRequired);
@@ -521,7 +537,7 @@
         mCarrierConfig.putBoolean(KEY_CARRIER_SUPPORTS_TETHERING_BOOL, false);
         when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mCarrierConfig);
         final TetheringConfiguration cfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
 
         if (SdkLevel.isAtLeastT()) {
             assertFalse(cfg.isCarrierSupportTethering);
@@ -535,13 +551,13 @@
     @Test
     public void testEnableLegacyWifiP2PAddress() throws Exception {
         final TetheringConfiguration defaultCfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertFalse(defaultCfg.shouldEnableWifiP2pDedicatedIp());
 
         when(mResources.getBoolean(R.bool.config_tether_enable_legacy_wifi_p2p_dedicated_ip))
                 .thenReturn(true);
         final TetheringConfiguration testCfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertTrue(testCfg.shouldEnableWifiP2pDedicatedIp());
     }
 
@@ -576,16 +592,13 @@
     public void testChooseUpstreamAutomatically_FlagOverride() throws Exception {
         when(mResources.getBoolean(R.bool.config_tether_upstream_automatic))
                 .thenReturn(false);
-        setTetherForceUpstreamAutomaticFlagVersion(TEST_PACKAGE_VERSION - 1);
-        assertTrue(DeviceConfigUtils.isFeatureEnabled(mMockContext, NAMESPACE_CONNECTIVITY,
-                TetheringConfiguration.TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION, APEX_NAME, false));
-
+        setTetherForceUpstreamAutomaticFlagEnabled(true);
         assertChooseUpstreamAutomaticallyIs(true);
 
-        setTetherForceUpstreamAutomaticFlagVersion(0L);
+        setTetherForceUpstreamAutomaticFlagEnabled(null);
         assertChooseUpstreamAutomaticallyIs(false);
 
-        setTetherForceUpstreamAutomaticFlagVersion(Long.MAX_VALUE);
+        setTetherForceUpstreamAutomaticFlagEnabled(false);
         assertChooseUpstreamAutomaticallyIs(false);
     }
 
@@ -593,7 +606,7 @@
     public void testChooseUpstreamAutomatically_FlagOverrideOnSAndT() throws Exception {
         when(mResources.getBoolean(R.bool.config_tether_upstream_automatic))
                 .thenReturn(false);
-        setTetherForceUpstreamAutomaticFlagVersion(TEST_PACKAGE_VERSION - 1);
+        setTetherForceUpstreamAutomaticFlagEnabled(true);
         assertChooseUpstreamAutomaticallyIs(false);
     }
 
@@ -604,28 +617,24 @@
         // TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION is.
         when(mResources.getBoolean(R.bool.config_tether_upstream_automatic))
                 .thenReturn(false);
-        setTetherForceUpstreamAutomaticFlagVersion(TEST_PACKAGE_VERSION - 1);
-        assertTrue(DeviceConfigUtils.isFeatureEnabled(mMockContext, NAMESPACE_CONNECTIVITY,
-                TetheringConfiguration.TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION, APEX_NAME, false));
-
+        setTetherForceUpstreamAutomaticFlagEnabled(true);
         assertChooseUpstreamAutomaticallyIs(true);
 
-        setTetherForceUpstreamAutomaticFlagVersion(0L);
+        setTetherForceUpstreamAutomaticFlagEnabled(null);
         assertChooseUpstreamAutomaticallyIs(true);
 
-        setTetherForceUpstreamAutomaticFlagVersion(Long.MAX_VALUE);
+        setTetherForceUpstreamAutomaticFlagEnabled(false);
         assertChooseUpstreamAutomaticallyIs(true);
     }
 
-    private void setTetherForceUpstreamAutomaticFlagVersion(Long version) {
-        doReturn(version == null ? null : Long.toString(version)).when(
-                () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
-                        eq(TetheringConfiguration.TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION)));
+    private void setTetherForceUpstreamAutomaticFlagEnabled(Boolean enabled) {
+        mDeps.setFeatureEnabled(
+                TetheringConfiguration.TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION, enabled);
     }
 
     private void assertChooseUpstreamAutomaticallyIs(boolean value) {
-        assertEquals(value, new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID)
-                .chooseUpstreamAutomatically);
+        assertEquals(value, new TetheringConfiguration(
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps).chooseUpstreamAutomatically);
     }
 
     @Test
@@ -654,7 +663,7 @@
 
     private void assertIsUsingNcm(boolean expected) {
         final TetheringConfiguration cfg =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertEquals(expected, cfg.isUsingNcm());
     }
 
@@ -704,7 +713,7 @@
 
     private void assertUsbAndNcmRegexs(final String[] usbRegexs, final String[] ncmRegexs) {
         final TetheringConfiguration cfg =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertArrayEquals(usbRegexs, cfg.tetherableUsbRegexs);
         assertArrayEquals(ncmRegexs, cfg.tetherableNcmRegexs);
     }
@@ -716,28 +725,28 @@
 
         final int defaultSubnetPrefixLength = 0;
         final TetheringConfiguration defaultCfg =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertEquals(defaultSubnetPrefixLength, defaultCfg.getP2pLeasesSubnetPrefixLength());
 
         final int prefixLengthTooSmall = -1;
         when(mResources.getInteger(R.integer.config_p2p_leases_subnet_prefix_length)).thenReturn(
                 prefixLengthTooSmall);
         final TetheringConfiguration tooSmallCfg =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertEquals(defaultSubnetPrefixLength, tooSmallCfg.getP2pLeasesSubnetPrefixLength());
 
         final int prefixLengthTooLarge = 31;
         when(mResources.getInteger(R.integer.config_p2p_leases_subnet_prefix_length)).thenReturn(
                 prefixLengthTooLarge);
         final TetheringConfiguration tooLargeCfg =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertEquals(defaultSubnetPrefixLength, tooLargeCfg.getP2pLeasesSubnetPrefixLength());
 
         final int p2pLeasesSubnetPrefixLength = 27;
         when(mResources.getInteger(R.integer.config_p2p_leases_subnet_prefix_length)).thenReturn(
                 p2pLeasesSubnetPrefixLength);
         final TetheringConfiguration p2pCfg =
-                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps);
         assertEquals(p2pLeasesSubnetPrefixLength, p2pCfg.getP2pLeasesSubnetPrefixLength());
     }
 }
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
index 75c819b..ac3d713 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
@@ -156,6 +156,7 @@
     @After
     fun tearDown() {
         fakeTetheringThread.quitSafely()
+        fakeTetheringThread.join()
     }
 
     private fun verifyActivityPendingIntent(intent: Intent, flags: Int) {
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
index 38f1e9c..da81bda 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -666,7 +666,7 @@
 
             // Calling System.gc() or System.runFinalization() doesn't guarantee GCs or finalizers
             // are executed synchronously. The finalizer is called after GC on a separate thread.
-            final int attempts = 100;
+            final int attempts = 600;
             final long waitIntervalMs = 50;
             for (int i = 0; i < attempts; i++) {
                 forceGc();
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index a8d886b..bd8b325 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -29,6 +29,7 @@
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
@@ -67,7 +68,7 @@
 import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
 import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
 import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
-import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0;
+import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_0;
 import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_NONE;
 import static com.android.networkstack.tethering.TestConnectivityManager.BROADCAST_FIRST;
 import static com.android.networkstack.tethering.TestConnectivityManager.CALLBACKS_FIRST;
@@ -82,6 +83,8 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
@@ -179,6 +182,7 @@
 import android.util.ArraySet;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -258,6 +262,8 @@
 
     private static final int DHCPSERVER_START_TIMEOUT_MS = 1000;
 
+    private static final Network[] NULL_NETWORK = new Network[] {null};
+
     @Mock private ApplicationInfo mApplicationInfo;
     @Mock private Context mContext;
     @Mock private NetworkStatsManager mStatsManager;
@@ -300,6 +306,7 @@
     private MockContentResolver mContentResolver;
     private BroadcastReceiver mBroadcastReceiver;
     private Tethering mTethering;
+    private TestTetheringEventCallback mTetheringEventCallback;
     private PhoneStateListener mPhoneStateListener;
     private InterfaceConfigurationParcel mInterfaceConfiguration;
     private TetheringConfiguration mConfig;
@@ -642,8 +649,7 @@
         mInterfaceConfiguration.flags = new String[0];
         when(mRouterAdvertisementDaemon.start())
                 .thenReturn(true);
-        initOffloadConfiguration(true /* offloadConfig */, OFFLOAD_HAL_VERSION_1_0,
-                0 /* defaultDisabled */);
+        initOffloadConfiguration(OFFLOAD_HAL_VERSION_HIDL_1_0, 0 /* defaultDisabled */);
         when(mOffloadHardwareInterface.getForwardedStats(any())).thenReturn(mForwardedStats);
 
         mServiceContext = new TestContext(mContext);
@@ -667,6 +673,7 @@
         verify(mStatsManager, times(1)).registerNetworkStatsProvider(anyString(), any());
         verify(mNetd).registerUnsolicitedEventListener(any());
         verifyDefaultNetworkRequestFiled();
+        mTetheringEventCallback = registerTetheringEventCallback();
 
         final ArgumentCaptor<PhoneStateListener> phoneListenerCaptor =
                 ArgumentCaptor.forClass(PhoneStateListener.class);
@@ -742,6 +749,16 @@
         return request;
     }
 
+    @NonNull
+    private TestTetheringEventCallback registerTetheringEventCallback() {
+        TestTetheringEventCallback callback = new TestTetheringEventCallback();
+        mTethering.registerTetheringEventCallback(callback);
+        mLooper.dispatchAll();
+        // Pull the first event which is filed immediately after the callback registration.
+        callback.expectUpstreamChanged(NULL_NETWORK);
+        return callback;
+    }
+
     @After
     public void tearDown() {
         mServiceContext.unregisterReceiver(mBroadcastReceiver);
@@ -921,9 +938,9 @@
         // tetherMatchingInterfaces() which starts by fetching all interfaces).
         verify(mNetd, times(1)).interfaceGetList();
 
-        // UpstreamNetworkMonitor should receive selected upstream
+        // Event callback should receive selected upstream
         verify(mUpstreamNetworkMonitor, times(1)).getCurrentPreferredUpstream();
-        verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(upstreamState.network);
+        mTetheringEventCallback.expectUpstreamChanged(upstreamState.network);
     }
 
     @Test
@@ -1178,7 +1195,7 @@
         verify(mUpstreamNetworkMonitor, times(1)).getCurrentPreferredUpstream();
         verify(mUpstreamNetworkMonitor, never()).selectPreferredUpstreamType(any());
 
-        verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(upstreamState.network);
+        mTetheringEventCallback.expectUpstreamChanged(upstreamState.network);
     }
 
     private void verifyDisableTryCellWhenTetheringStop(InOrder inOrder) {
@@ -1203,14 +1220,14 @@
         mobile.fakeConnect();
         mCm.makeDefaultNetwork(mobile, BROADCAST_FIRST);
         mLooper.dispatchAll();
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId);
+        mTetheringEventCallback.expectUpstreamChanged(mobile.networkId);
 
         // Switch upstream to wifi.
         wifi.fakeConnect();
         mCm.makeDefaultNetwork(wifi, BROADCAST_FIRST);
         mLooper.dispatchAll();
         inOrder.verify(mUpstreamNetworkMonitor).setTryCell(false);
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(wifi.networkId);
+        mTetheringEventCallback.expectUpstreamChanged(wifi.networkId);
     }
 
     private void verifyAutomaticUpstreamSelection(boolean configAutomatic) throws Exception {
@@ -1227,30 +1244,30 @@
         // Switch upstreams a few times.
         mCm.makeDefaultNetwork(mobile, BROADCAST_FIRST, doDispatchAll);
         mLooper.dispatchAll();
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId);
+        mTetheringEventCallback.expectUpstreamChanged(mobile.networkId);
 
         mCm.makeDefaultNetwork(wifi, BROADCAST_FIRST, doDispatchAll);
         mLooper.dispatchAll();
         inOrder.verify(mUpstreamNetworkMonitor).setTryCell(false);
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(wifi.networkId);
+        mTetheringEventCallback.expectUpstreamChanged(wifi.networkId);
 
         mCm.makeDefaultNetwork(mobile, CALLBACKS_FIRST);
         mLooper.dispatchAll();
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId);
+        mTetheringEventCallback.expectUpstreamChanged(mobile.networkId);
 
         mCm.makeDefaultNetwork(wifi, CALLBACKS_FIRST);
         mLooper.dispatchAll();
         inOrder.verify(mUpstreamNetworkMonitor).setTryCell(false);
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(wifi.networkId);
+        mTetheringEventCallback.expectUpstreamChanged(wifi.networkId);
 
         mCm.makeDefaultNetwork(mobile, CALLBACKS_FIRST, doDispatchAll);
         mLooper.dispatchAll();
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId);
+        mTetheringEventCallback.expectUpstreamChanged(mobile.networkId);
 
         // Wifi disconnecting should not have any affect since it's not the current upstream.
         wifi.fakeDisconnect();
         mLooper.dispatchAll();
-        inOrder.verify(mUpstreamNetworkMonitor, never()).setCurrentUpstream(any());
+        mTetheringEventCallback.assertNoUpstreamChangeCallback();
 
         // Lose and regain upstream.
         assertTrue(mUpstreamNetworkMonitor.getCurrentPreferredUpstream().linkProperties
@@ -1260,13 +1277,13 @@
         mobile.fakeDisconnect();
         mLooper.dispatchAll();
         inOrder.verify(mUpstreamNetworkMonitor).setTryCell(true);
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(null);
+        mTetheringEventCallback.expectUpstreamChanged(NULL_NETWORK);
 
         mobile = new TestNetworkAgent(mCm, buildMobile464xlatUpstreamState());
         mobile.fakeConnect();
         mCm.makeDefaultNetwork(mobile, BROADCAST_FIRST, doDispatchAll);
         mLooper.dispatchAll();
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId);
+        mTetheringEventCallback.expectUpstreamChanged(mobile.networkId);
 
         // Check the IP addresses to ensure that the upstream is indeed not the same as the previous
         // mobile upstream, even though the netId is (unrealistically) the same.
@@ -1278,13 +1295,13 @@
         mobile.fakeDisconnect();
         mLooper.dispatchAll();
         inOrder.verify(mUpstreamNetworkMonitor).setTryCell(true);
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(null);
+        mTetheringEventCallback.expectUpstreamChanged(NULL_NETWORK);
 
         mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
         mobile.fakeConnect();
         mCm.makeDefaultNetwork(mobile, CALLBACKS_FIRST, doDispatchAll);
         mLooper.dispatchAll();
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId);
+        mTetheringEventCallback.expectUpstreamChanged(mobile.networkId);
 
         assertTrue(mUpstreamNetworkMonitor.getCurrentPreferredUpstream().linkProperties
                 .hasIPv4Address());
@@ -1325,18 +1342,143 @@
         mLooper.dispatchAll();
         mCm.makeDefaultNetwork(mobile, CALLBACKS_FIRST, null);
         mLooper.dispatchAll();
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(mobile.networkId);
+        mTetheringEventCallback.expectUpstreamChanged(mobile.networkId);
 
         wifi.fakeConnect();
         mLooper.dispatchAll();
         mCm.makeDefaultNetwork(wifi, CALLBACKS_FIRST, null);
         mLooper.dispatchAll();
         inOrder.verify(mUpstreamNetworkMonitor).setTryCell(false);
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(wifi.networkId);
+        mTetheringEventCallback.expectUpstreamChanged(wifi.networkId);
 
         verifyDisableTryCellWhenTetheringStop(inOrder);
     }
 
+    private void verifyWifiUpstreamAndUnregisterDunCallback(@NonNull final InOrder inOrder,
+            @NonNull final TestNetworkAgent wifi, @NonNull final NetworkCallback currentDunCallack)
+            throws Exception {
+        assertNotNull(currentDunCallack);
+
+        inOrder.verify(mUpstreamNetworkMonitor).setTryCell(false);
+        inOrder.verify(mCm).unregisterNetworkCallback(eq(currentDunCallack));
+        mTetheringEventCallback.expectUpstreamChanged(wifi.networkId);
+        mTetheringEventCallback.assertNoUpstreamChangeCallback();
+    }
+
+    @Nullable
+    private NetworkCallback verifyDunUpstream(@NonNull final InOrder inOrder,
+            @NonNull final TestNetworkAgent dun, final boolean needToRequestNetwork)
+            throws Exception {
+        inOrder.verify(mUpstreamNetworkMonitor).setTryCell(true);
+        ArgumentCaptor<NetworkCallback> captor = ArgumentCaptor.forClass(NetworkCallback.class);
+        NetworkCallback dunNetworkCallback = null;
+        if (needToRequestNetwork) {
+            inOrder.verify(mCm).requestNetwork(any(), eq(0), eq(TYPE_MOBILE_DUN), any(),
+                    captor.capture());
+            dunNetworkCallback = captor.getValue();
+        }
+        mTetheringEventCallback.expectUpstreamChanged(NULL_NETWORK);
+        final Runnable doDispatchAll = () -> mLooper.dispatchAll();
+        dun.fakeConnect(CALLBACKS_FIRST, doDispatchAll);
+        mLooper.dispatchAll();
+        mTetheringEventCallback.expectUpstreamChanged(dun.networkId);
+
+        if (needToRequestNetwork) {
+            assertNotNull(dunNetworkCallback);
+        } else {
+            assertNull(dunNetworkCallback);
+        }
+
+        return dunNetworkCallback;
+    }
+
+    // Overall test coverage:
+    // - verifyChooseDunUpstreamByAutomaticMode: common, test#1, test#2
+    // - testChooseDunUpstreamByAutomaticMode_defaultNetworkWifi: test#3, test#4
+    // - testChooseDunUpstreamByAutomaticMode_loseDefaultNetworkWifi: test#5
+    // - testChooseDunUpstreamByAutomaticMode_defaultNetworkCell: test#5, test#7
+    // - testChooseDunUpstreamByAutomaticMode_loseAndRegainDun: test#8
+    // - testChooseDunUpstreamByAutomaticMode_switchDefaultFromWifiToCell: test#9, test#10
+    //
+    // Overall test cases:
+    // +-------+-------+-------+-------+-------+
+    // | Test  | WiFi  | Cellu |  Dun  | Expec |
+    // | Case  |       | alr   |       | ted   |
+    // |   #   |       |       |       | Upstr |
+    // |       |       |       |       | eam   |
+    // +-------+-------+-------+-------+-------+
+    // |   -   |       |       |       |   -   | --+
+    // +-------+-------+-------+-------+-------+   |
+    // |   -   |       |   V   |       |   -   |   |
+    // +-------+-------+-------+-------+-------+   |
+    // |   -   |       |   V   |   O   |  Dun  |   +-- chooseDunUpstreamTestCommon
+    // +-------+-------+-------+-------+-------+   |
+    // |   -   |   V   |   O   |   O   |  WiFi |   |
+    // +-------+-------+-------+-------+-------+   |
+    // |   -   |   V   |   O   |       |  WiFi | --+
+    // +-------+-------+-------+-------+-------+
+    // |       |   O   |   V   |       |   -   |
+    // |   1   +-------+-------+-------+-------+
+    // |       |   O   |   V   |   O   |  Dun  |
+    // +-------+-------+-------+-------+-------+
+    // |       |   O   |   V   |       |   -   |
+    // |   2   +-------+-------+-------+-------+
+    // |       |   O   |   V   |   O   |  Dun  |
+    // +-------+-------+-------+-------+-------+
+    // |   3   |   V   |   O   |       |  WiFi |
+    // +-------+-------+-------+-------+-------+
+    // |   4   |   V   |       |       |  WiFi |
+    // +-------+-------+-------+-------+-------+
+    // |   5   |       |       |   O   |  Dun  |
+    // +-------+-------+-------+-------+-------+
+    // |   6   |       |   V   |   O   |  Dun  |
+    // +-------+-------+-------+-------+-------+
+    // |   7   |       |       |   O   |  Dun  |
+    // +-------+-------+-------+-------+-------+
+    // |       |       |       |       |   -   |
+    // |   8   +-------+-------+-------+-------+
+    // |       |       |       |   O   |  Dun  |
+    // +-------+-------+-------+-------+-------+
+    // |       |   V   |       |   O   |  WiFi |
+    // |   9   +-------+-------+-------+-------+
+    // |       |   V   |       |       |  WiFi |
+    // +-------+-------+-------+-------+-------+
+    // |       |   O   |   V   |       |   -   |
+    // |   10  +-------+-------+-------+-------+
+    // |       |   O   |   V   |   O   |  Dun  |
+    // +-------+-------+-------+-------+-------+
+    //
+    // Annotation:
+    // 1. "V" means that the given network is connected and it is default network.
+    // 2. "O" means that the given network is connected and it is not default network.
+    //
+
+    // Test case:
+    // +-------+-------+-------+-------+-------+
+    // | Test  | WiFi  | Cellu |  Dun  | Expec |
+    // | Case  |       | alr   |       | ted   |
+    // |   #   |       |       |       | Upstr |
+    // |       |       |       |       | eam   |
+    // +-------+-------+-------+-------+-------+
+    // |   -   |       |       |       |   -   | --+
+    // +-------+-------+-------+-------+-------+   |
+    // |   -   |       |   V   |       |   -   |   |
+    // +-------+-------+-------+-------+-------+   |
+    // |   -   |       |   V   |   O   |  Dun  |   +-- chooseDunUpstreamTestCommon
+    // +-------+-------+-------+-------+-------+   |
+    // |   -   |   V   |   O   |   O   |  WiFi |   |
+    // +-------+-------+-------+-------+-------+   |
+    // |   -   |   V   |   O   |       |  WiFi | --+
+    // +-------+-------+-------+-------+-------+
+    // |       |   O   |   V   |       |   -   |
+    // |   1   +-------+-------+-------+-------+
+    // |       |   O   |   V   |   O   |  Dun  |
+    // +-------+-------+-------+-------+-------+
+    // |       |   O   |   V   |       |   -   |
+    // |   2   +-------+-------+-------+-------+
+    // |       |   O   |   V   |   O   |  Dun  |
+    // +-------+-------+-------+-------+-------+
+    //
     private void verifyChooseDunUpstreamByAutomaticMode(boolean configAutomatic) throws Exception {
         // Enable automatic upstream selection.
         TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
@@ -1345,28 +1487,22 @@
         InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
         chooseDunUpstreamTestCommon(configAutomatic, inOrder, mobile, wifi, dun);
 
-        // When default network switch to mobile and wifi is connected (may have low signal),
+        // [1] When default network switch to mobile and wifi is connected (may have low signal),
         // automatic mode would request dun again and choose it as upstream.
         mCm.makeDefaultNetwork(mobile, CALLBACKS_FIRST);
         mLooper.dispatchAll();
-        inOrder.verify(mUpstreamNetworkMonitor).setTryCell(true);
-        ArgumentCaptor<NetworkCallback> captor = ArgumentCaptor.forClass(NetworkCallback.class);
-        inOrder.verify(mCm).requestNetwork(any(), eq(0), eq(TYPE_MOBILE_DUN), any(), any());
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(null);
-        final Runnable doDispatchAll = () -> mLooper.dispatchAll();
-        dun.fakeConnect(CALLBACKS_FIRST, doDispatchAll);
-        mLooper.dispatchAll();
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(dun.networkId);
+        verifyDunUpstream(inOrder, dun, true /* needToRequestNetwork */);
 
-        // Lose and regain upstream again.
+        // [2] Lose and regain upstream again.
+        final Runnable doDispatchAll = () -> mLooper.dispatchAll();
         dun.fakeDisconnect(CALLBACKS_FIRST, doDispatchAll);
         mLooper.dispatchAll();
         inOrder.verify(mUpstreamNetworkMonitor).setTryCell(true);
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(null);
+        mTetheringEventCallback.expectUpstreamChanged(NULL_NETWORK);
         inOrder.verify(mCm, never()).unregisterNetworkCallback(any(NetworkCallback.class));
         dun.fakeConnect(CALLBACKS_FIRST, doDispatchAll);
         mLooper.dispatchAll();
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(dun.networkId);
+        mTetheringEventCallback.expectUpstreamChanged(dun.networkId);
 
         verifyDisableTryCellWhenTetheringStop(inOrder);
     }
@@ -1376,12 +1512,245 @@
         verifyChooseDunUpstreamByAutomaticMode(true /* configAutomatic */);
     }
 
+    // testChooseDunUpstreamByAutomaticMode_* doesn't verify configAutomatic:false because no
+    // matter |configAutomatic| set to true or false, the result always be automatic mode. We
+    // just need one test to make sure this behavior. Don't need to test this configuration
+    // in all tests.
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
     public void testChooseDunUpstreamByAutomaticModeWithConfigDisabled() throws Exception {
         verifyChooseDunUpstreamByAutomaticMode(false /* configAutomatic */);
     }
 
+    // Test case:
+    // +-------+-------+-------+-------+-------+
+    // | Test  | WiFi  | Cellu |  Dun  | Expec |
+    // | Case  |       | alr   |       | ted   |
+    // |   #   |       |       |       | Upstr |
+    // |       |       |       |       | eam   |
+    // +-------+-------+-------+-------+-------+
+    // |   -   |   O   |   V   |   O   |  Dun  |
+    // +-------+-------+-------+-------+-------+
+    // |   3   |   V   |   O   |       |  WiFi |
+    // +-------+-------+-------+-------+-------+
+    // |   4   |   V   |       |       |  WiFi |
+    // +-------+-------+-------+-------+-------+
+    //
+    // See verifyChooseDunUpstreamByAutomaticMode for the annotation.
+    //
+    @Test
+    public void testChooseDunUpstreamByAutomaticMode_defaultNetworkWifi() throws Exception {
+        TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
+        TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
+        TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState());
+        final InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
+        final NetworkCallback dunNetworkCallback1 = setupDunUpstreamTest(
+                true /* configAutomatic */, inOrder);
+
+        // When wifi connected, unregister dun request and choose wifi as upstream.
+        wifi.fakeConnect();
+        mCm.makeDefaultNetwork(wifi, CALLBACKS_FIRST);
+        mLooper.dispatchAll();
+        verifyWifiUpstreamAndUnregisterDunCallback(inOrder, wifi, dunNetworkCallback1);
+
+        // When default network switch to mobile and wifi is connected (may have low signal),
+        // automatic mode would request dun again and choose it as upstream.
+        mCm.makeDefaultNetwork(mobile, CALLBACKS_FIRST);
+        mLooper.dispatchAll();
+        final NetworkCallback dunNetworkCallback2 = verifyDunUpstream(inOrder, dun,
+                true /* needToRequestNetwork */);
+
+        // [3] When default network switch to wifi and mobile is still connected,
+        // unregister dun request and choose wifi as upstream.
+        mCm.makeDefaultNetwork(wifi, CALLBACKS_FIRST);
+        mLooper.dispatchAll();
+        verifyWifiUpstreamAndUnregisterDunCallback(inOrder, wifi, dunNetworkCallback2);
+
+        // [4] When mobile is disconnected, keep wifi as upstream.
+        final Runnable doDispatchAll = () -> mLooper.dispatchAll();
+        mobile.fakeDisconnect(CALLBACKS_FIRST, doDispatchAll);
+        mLooper.dispatchAll();
+        mTetheringEventCallback.assertNoUpstreamChangeCallback();
+
+        verifyDisableTryCellWhenTetheringStop(inOrder);
+    }
+
+    // Test case:
+    // +-------+-------+-------+-------+-------+
+    // | Test  | WiFi  | Cellu |  Dun  | Expec |
+    // | Case  |       | alr   |       | ted   |
+    // |   #   |       |       |       | Upstr |
+    // |       |       |       |       | eam   |
+    // +-------+-------+-------+-------+-------+
+    // |   -   |   V   |       |       |  WiFi |
+    // +-------+-------+-------+-------+-------+
+    // |   5   |       |       |   O   |  Dun  |
+    // +-------+-------+-------+-------+-------+
+    //
+    // See verifyChooseDunUpstreamByAutomaticMode for the annotation.
+    //
+    @Test
+    public void testChooseDunUpstreamByAutomaticMode_loseDefaultNetworkWifi() throws Exception {
+        TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
+        TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState());
+        final InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
+        final NetworkCallback dunNetworkCallback = setupDunUpstreamTest(
+                true /* configAutomatic */, inOrder);
+
+        // When wifi connected, unregister dun request and choose wifi as upstream.
+        wifi.fakeConnect();
+        mCm.makeDefaultNetwork(wifi, CALLBACKS_FIRST);
+        mLooper.dispatchAll();
+        verifyWifiUpstreamAndUnregisterDunCallback(inOrder, wifi, dunNetworkCallback);
+
+        // [5] When wifi is disconnected, automatic mode would request dun again and choose it
+        // as upstream.
+        final Runnable doDispatchAll = () -> mLooper.dispatchAll();
+        mCm.makeDefaultNetwork(null, CALLBACKS_FIRST, doDispatchAll);
+        wifi.fakeDisconnect(CALLBACKS_FIRST, doDispatchAll);
+        mLooper.dispatchAll();
+        verifyDunUpstream(inOrder, dun, true /* needToRequestNetwork */);
+
+        verifyDisableTryCellWhenTetheringStop(inOrder);
+    }
+
+    // Test case:
+    // +-------+-------+-------+-------+-------+
+    // | Test  | WiFi  | Cellu |  Dun  | Expec |
+    // | Case  |       | alr   |       | ted   |
+    // |   #   |       |       |       | Upstr |
+    // |       |       |       |       | eam   |
+    // +-------+-------+-------+-------+-------+
+    // |   -   |       |       |   O   |  Dun  |
+    // +-------+-------+-------+-------+-------+
+    // |   6   |       |   V   |   O   |  Dun  |
+    // +-------+-------+-------+-------+-------+
+    // |   7   |       |       |   O   |  Dun  |
+    // +-------+-------+-------+-------+-------+
+    //
+    // See verifyChooseDunUpstreamByAutomaticMode for the annotation.
+    //
+    @Test
+    public void testChooseDunUpstreamByAutomaticMode_defaultNetworkCell() throws Exception {
+        TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
+        TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState());
+        final InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
+        setupDunUpstreamTest(true /* configAutomatic */, inOrder);
+
+        // Pretend dun connected and expect choose dun as upstream.
+        final Runnable doDispatchAll = () -> mLooper.dispatchAll();
+        dun.fakeConnect(CALLBACKS_FIRST, doDispatchAll);
+        mLooper.dispatchAll();
+        mTetheringEventCallback.expectUpstreamChanged(dun.networkId);
+
+        // [6] When mobile is connected and default network switch to mobile, keep dun as upstream.
+        mobile.fakeConnect();
+        mCm.makeDefaultNetwork(mobile, CALLBACKS_FIRST);
+        mLooper.dispatchAll();
+        mTetheringEventCallback.assertNoUpstreamChangeCallback();
+
+        // [7] When mobile is disconnected, keep dun as upstream.
+        mCm.makeDefaultNetwork(null, CALLBACKS_FIRST, doDispatchAll);
+        mobile.fakeDisconnect(CALLBACKS_FIRST, doDispatchAll);
+        mLooper.dispatchAll();
+        mTetheringEventCallback.assertNoUpstreamChangeCallback();
+
+        verifyDisableTryCellWhenTetheringStop(inOrder);
+    }
+
+    // Test case:
+    // +-------+-------+-------+-------+-------+
+    // | Test  | WiFi  | Cellu |  Dun  | Expec |
+    // | Case  |       | alr   |       | ted   |
+    // |   #   |       |       |       | Upstr |
+    // |       |       |       |       | eam   |
+    // +-------+-------+-------+-------+-------+
+    // |   -   |       |       |   O   |  Dun  |
+    // +-------+-------+-------+-------+-------+
+    // |       |       |       |       |   -   |
+    // |   8   +-------+-------+-------+-------+
+    // |       |       |       |   O   |  Dun  |
+    // +-------+-------+-------+-------+-------+
+    //
+    // See verifyChooseDunUpstreamByAutomaticMode for the annotation.
+    //
+    @Test
+    public void testChooseDunUpstreamByAutomaticMode_loseAndRegainDun() throws Exception {
+        TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState());
+        final InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
+        setupDunUpstreamTest(true /* configAutomatic */, inOrder);
+
+        // Pretend dun connected and expect choose dun as upstream.
+        final Runnable doDispatchAll = () -> mLooper.dispatchAll();
+        dun.fakeConnect(CALLBACKS_FIRST, doDispatchAll);
+        mLooper.dispatchAll();
+        mTetheringEventCallback.expectUpstreamChanged(dun.networkId);
+
+        // [8] Lose and regain upstream again.
+        dun.fakeDisconnect(CALLBACKS_FIRST, doDispatchAll);
+        mLooper.dispatchAll();
+        verifyDunUpstream(inOrder, dun, false /* needToRequestNetwork */);
+
+        verifyDisableTryCellWhenTetheringStop(inOrder);
+    }
+
+    // Test case:
+    // +-------+-------+-------+-------+-------+
+    // | Test  | WiFi  | Cellu |  Dun  | Expec |
+    // | Case  |       | alr   |       | ted   |
+    // |   #   |       |       |       | Upstr |
+    // |       |       |       |       | eam   |
+    // +-------+-------+-------+-------+-------+
+    // |   -   |       |       |   O   |  Dun  |
+    // +-------+-------+-------+-------+-------+
+    // |       |   V   |       |   O   |  WiFi |
+    // |   9   +-------+-------+-------+-------+
+    // |       |   V   |       |       |  WiFi |
+    // +-------+-------+-------+-------+-------+
+    // |       |   O   |   V   |       |   -   |
+    // |   10  +-------+-------+-------+-------+
+    // |       |   O   |   V   |   O   |  Dun  |
+    // +-------+-------+-------+-------+-------+
+    //
+    // See verifyChooseDunUpstreamByAutomaticMode for the annotation.
+    //
+    @Test
+    public void testChooseDunUpstreamByAutomaticMode_switchDefaultFromWifiToCell()
+            throws Exception {
+        TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState());
+        TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
+        TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState());
+        final InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor);
+        final NetworkCallback dunNetworkCallback = setupDunUpstreamTest(
+                true /* configAutomatic */, inOrder);
+
+        // Pretend dun connected and expect choose dun as upstream.
+        final Runnable doDispatchAll = () -> mLooper.dispatchAll();
+        dun.fakeConnect(CALLBACKS_FIRST, doDispatchAll);
+        mLooper.dispatchAll();
+        mTetheringEventCallback.expectUpstreamChanged(dun.networkId);
+
+        // [9] When wifi is connected and default network switch to wifi, unregister dun request
+        // and choose wifi as upstream. When dun is disconnected, keep wifi as upstream.
+        wifi.fakeConnect();
+        mCm.makeDefaultNetwork(wifi, CALLBACKS_FIRST);
+        mLooper.dispatchAll();
+        verifyWifiUpstreamAndUnregisterDunCallback(inOrder, wifi, dunNetworkCallback);
+        dun.fakeDisconnect(CALLBACKS_FIRST, doDispatchAll);
+        mLooper.dispatchAll();
+        mTetheringEventCallback.assertNoUpstreamChangeCallback();
+
+        // [10] When mobile and mobile are connected and default network switch to mobile
+        // (may have low signal), automatic mode would request dun again and choose it as
+        // upstream.
+        mobile.fakeConnect();
+        mCm.makeDefaultNetwork(mobile, CALLBACKS_FIRST);
+        mLooper.dispatchAll();
+        verifyDunUpstream(inOrder, dun, true /* needToRequestNetwork */);
+
+        verifyDisableTryCellWhenTetheringStop(inOrder);
+    }
+
     @Test
     @IgnoreAfter(Build.VERSION_CODES.TIRAMISU)
     public void testChooseDunUpstreamByLegacyMode() throws Exception {
@@ -1397,7 +1766,7 @@
         mCm.makeDefaultNetwork(mobile, CALLBACKS_FIRST);
         mLooper.dispatchAll();
         inOrder.verify(mUpstreamNetworkMonitor).setTryCell(false);
-        inOrder.verify(mUpstreamNetworkMonitor, never()).setCurrentUpstream(any());
+        mTetheringEventCallback.assertNoUpstreamChangeCallback();
         // BUG: when wifi disconnect, the dun request would not be filed again because wifi is
         // no longer be default network which do not have CONNECTIVIY_ACTION broadcast.
         wifi.fakeDisconnect();
@@ -1425,8 +1794,7 @@
         verifyDisableTryCellWhenTetheringStop(inOrder);
     }
 
-    private void chooseDunUpstreamTestCommon(final boolean automatic, InOrder inOrder,
-            TestNetworkAgent mobile, TestNetworkAgent wifi, TestNetworkAgent dun) throws Exception {
+    private NetworkCallback setupDunUpstreamTest(final boolean automatic, InOrder inOrder) {
         when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(automatic);
         when(mTelephonyManager.isTetheringApnRequired()).thenReturn(true);
         sendConfigurationChanged();
@@ -1439,31 +1807,36 @@
         inOrder.verify(mUpstreamNetworkMonitor).setTryCell(true);
         ArgumentCaptor<NetworkCallback> captor = ArgumentCaptor.forClass(NetworkCallback.class);
         inOrder.verify(mCm).requestNetwork(any(), eq(0), eq(TYPE_MOBILE_DUN), any(),
-                captor.capture());
-        final NetworkCallback dunNetworkCallback1 = captor.getValue();
+                captor.capture() /* DUN network callback */);
 
-        // Pretend cellular connected and expect the upstream to be set.
+        return captor.getValue();
+    }
+
+    private void chooseDunUpstreamTestCommon(final boolean automatic, InOrder inOrder,
+            TestNetworkAgent mobile, TestNetworkAgent wifi, TestNetworkAgent dun)
+            throws Exception {
+        final NetworkCallback dunNetworkCallback = setupDunUpstreamTest(automatic, inOrder);
+
+        // Pretend cellular connected and expect the upstream to be not set.
         mobile.fakeConnect();
         mCm.makeDefaultNetwork(mobile, BROADCAST_FIRST);
         mLooper.dispatchAll();
-        inOrder.verify(mUpstreamNetworkMonitor, never()).setCurrentUpstream(mobile.networkId);
+        mTetheringEventCallback.assertNoUpstreamChangeCallback();
 
         // Pretend dun connected and expect choose dun as upstream.
         final Runnable doDispatchAll = () -> mLooper.dispatchAll();
         dun.fakeConnect(BROADCAST_FIRST, doDispatchAll);
         mLooper.dispatchAll();
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(dun.networkId);
+        mTetheringEventCallback.expectUpstreamChanged(dun.networkId);
 
         // When wifi connected, unregister dun request and choose wifi as upstream.
         wifi.fakeConnect();
         mCm.makeDefaultNetwork(wifi, CALLBACKS_FIRST);
         mLooper.dispatchAll();
-        inOrder.verify(mUpstreamNetworkMonitor).setTryCell(false);
-        inOrder.verify(mCm).unregisterNetworkCallback(eq(dunNetworkCallback1));
-        inOrder.verify(mUpstreamNetworkMonitor).setCurrentUpstream(wifi.networkId);
+        verifyWifiUpstreamAndUnregisterDunCallback(inOrder, wifi, dunNetworkCallback);
         dun.fakeDisconnect(BROADCAST_FIRST, doDispatchAll);
         mLooper.dispatchAll();
-        inOrder.verify(mUpstreamNetworkMonitor, never()).setCurrentUpstream(any());
+        mTetheringEventCallback.assertNoUpstreamChangeCallback();
     }
 
     private void runNcmTethering() {
@@ -1627,6 +2000,7 @@
         verify(mWifiManager).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
 
+        verify(mTetheringMetrics, times(0)).maybeUpdateUpstreamType(any());
         verify(mTetheringMetrics, times(2)).updateErrorCode(eq(TETHERING_WIFI),
                 eq(TETHER_ERROR_INTERNAL_ERROR));
         verify(mTetheringMetrics, times(2)).sendReport(eq(TETHERING_WIFI));
@@ -1767,7 +2141,7 @@
                     new ArrayList<Network>(Arrays.asList(networks));
             for (Network upstream : expectedUpstreams) {
                 // throws OOB if no expectations
-                assertEquals(mActualUpstreams.remove(0), upstream);
+                assertEquals(upstream, mActualUpstreams.remove(0));
             }
             assertNoUpstreamChangeCallback();
         }
@@ -1782,14 +2156,14 @@
             for (TetheringConfigurationParcel config : expectedTetherConfig) {
                 // throws OOB if no expectations
                 final TetheringConfigurationParcel actualConfig = mTetheringConfigs.remove(0);
-                assertTetherConfigParcelEqual(actualConfig, config);
+                assertTetherConfigParcelEqual(config, actualConfig);
             }
             assertNoConfigChangeCallback();
         }
 
         public void expectOffloadStatusChanged(final int expectedStatus) {
             assertOffloadStatusChangedCallback();
-            assertEquals(mOffloadStatus.remove(0), new Integer(expectedStatus));
+            assertEquals(Integer.valueOf(expectedStatus), mOffloadStatus.remove(0));
         }
 
         public TetherStatesParcel pollTetherStatesChanged() {
@@ -1880,12 +2254,12 @@
 
         private void assertTetherConfigParcelEqual(@NonNull TetheringConfigurationParcel actual,
                 @NonNull TetheringConfigurationParcel expect) {
-            assertArrayEquals(actual.tetherableUsbRegexs, expect.tetherableUsbRegexs);
-            assertArrayEquals(actual.tetherableWifiRegexs, expect.tetherableWifiRegexs);
-            assertArrayEquals(actual.tetherableBluetoothRegexs, expect.tetherableBluetoothRegexs);
-            assertArrayEquals(actual.legacyDhcpRanges, expect.legacyDhcpRanges);
-            assertArrayEquals(actual.provisioningApp, expect.provisioningApp);
-            assertEquals(actual.provisioningAppNoUi, expect.provisioningAppNoUi);
+            assertArrayEquals(expect.tetherableUsbRegexs, actual.tetherableUsbRegexs);
+            assertArrayEquals(expect.tetherableWifiRegexs, actual.tetherableWifiRegexs);
+            assertArrayEquals(expect.tetherableBluetoothRegexs, actual.tetherableBluetoothRegexs);
+            assertArrayEquals(expect.legacyDhcpRanges, actual.legacyDhcpRanges);
+            assertArrayEquals(expect.provisioningApp, actual.provisioningApp);
+            assertEquals(expect.provisioningAppNoUi, actual.provisioningAppNoUi);
         }
     }
 
@@ -1910,7 +2284,7 @@
         mTethering.registerTetheringEventCallback(callback);
         mLooper.dispatchAll();
         callback.expectTetheredClientChanged(Collections.emptyList());
-        callback.expectUpstreamChanged(new Network[] {null});
+        callback.expectUpstreamChanged(NULL_NETWORK);
         callback.expectConfigurationChanged(
                 mTethering.getTetheringConfiguration().toStableParcelable());
         TetherStatesParcel tetherState = callback.pollTetherStatesChanged();
@@ -1958,7 +2332,7 @@
         tetherState = callback2.pollTetherStatesChanged();
         assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
         mLooper.dispatchAll();
-        callback2.expectUpstreamChanged(new Network[] {null});
+        callback2.expectUpstreamChanged(NULL_NETWORK);
         callback2.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
         callback.assertNoCallback();
     }
@@ -1971,25 +2345,15 @@
         mLooper.dispatchAll();
         callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
 
-        // 1. Offload fail if no OffloadConfig.
-        initOffloadConfiguration(false /* offloadConfig */, OFFLOAD_HAL_VERSION_1_0,
-                0 /* defaultDisabled */);
+        // 1. Offload fail if no IOffloadHal.
+        initOffloadConfiguration(OFFLOAD_HAL_VERSION_NONE, 0 /* defaultDisabled */);
         runUsbTethering(upstreamState);
         callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_FAILED);
         runStopUSBTethering();
         callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
         reset(mUsbManager, mIPv6TetheringCoordinator);
-        // 2. Offload fail if no OffloadControl.
-        initOffloadConfiguration(true /* offloadConfig */, OFFLOAD_HAL_VERSION_NONE,
-                0 /* defaultDisabled */);
-        runUsbTethering(upstreamState);
-        callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_FAILED);
-        runStopUSBTethering();
-        callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
-        reset(mUsbManager, mIPv6TetheringCoordinator);
-        // 3. Offload fail if disabled by settings.
-        initOffloadConfiguration(true /* offloadConfig */, OFFLOAD_HAL_VERSION_1_0,
-                1 /* defaultDisabled */);
+        // 2. Offload fail if disabled by settings.
+        initOffloadConfiguration(OFFLOAD_HAL_VERSION_HIDL_1_0, 1 /* defaultDisabled */);
         runUsbTethering(upstreamState);
         callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_FAILED);
         runStopUSBTethering();
@@ -2004,11 +2368,10 @@
         verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NONE);
     }
 
-    private void initOffloadConfiguration(final boolean offloadConfig,
-            @OffloadHardwareInterface.OffloadHalVersion final int offloadControlVersion,
+    private void initOffloadConfiguration(
+            @OffloadHardwareInterface.OffloadHalVersion final int offloadHalVersion,
             final int defaultDisabled) {
-        when(mOffloadHardwareInterface.initOffloadConfig()).thenReturn(offloadConfig);
-        when(mOffloadHardwareInterface.initOffloadControl(any())).thenReturn(offloadControlVersion);
+        when(mOffloadHardwareInterface.initOffload(any())).thenReturn(offloadHalVersion);
         when(mOffloadHardwareInterface.getDefaultTetherOffloadDisabled()).thenReturn(
                 defaultDisabled);
     }
@@ -2305,27 +2668,67 @@
     public void testUpstreamNetworkChanged() {
         final Tethering.TetherMainSM stateMachine = (Tethering.TetherMainSM)
                 mTetheringDependencies.mUpstreamNetworkMonitorSM;
+        final InOrder inOrder = inOrder(mNotificationUpdater);
+
+        // Gain upstream.
         final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
         initTetheringUpstream(upstreamState);
         stateMachine.chooseUpstreamType(true);
+        mTetheringEventCallback.expectUpstreamChanged(upstreamState.network);
+        inOrder.verify(mNotificationUpdater)
+                .onUpstreamCapabilitiesChanged(upstreamState.networkCapabilities);
 
-        verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(eq(upstreamState.network));
-        verify(mNotificationUpdater, times(1)).onUpstreamCapabilitiesChanged(any());
+        // Set the upstream with the same network ID but different object and the same capability.
+        final UpstreamNetworkState upstreamState2 = buildMobileIPv4UpstreamState();
+        initTetheringUpstream(upstreamState2);
+        stateMachine.chooseUpstreamType(true);
+        // Bug: duplicated upstream change event.
+        mTetheringEventCallback.expectUpstreamChanged(upstreamState2.network);
+        inOrder.verify(mNotificationUpdater)
+                .onUpstreamCapabilitiesChanged(upstreamState2.networkCapabilities);
+
+        // Set the upstream with the same network ID but different object and different capability.
+        final UpstreamNetworkState upstreamState3 = buildMobileIPv4UpstreamState();
+        assertFalse(upstreamState3.networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED));
+        upstreamState3.networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED);
+        initTetheringUpstream(upstreamState3);
+        stateMachine.chooseUpstreamType(true);
+        // Bug: duplicated upstream change event.
+        mTetheringEventCallback.expectUpstreamChanged(upstreamState3.network);
+        inOrder.verify(mNotificationUpdater)
+                .onUpstreamCapabilitiesChanged(upstreamState3.networkCapabilities);
+
+        // Lose upstream.
+        initTetheringUpstream(null);
+        stateMachine.chooseUpstreamType(true);
+        mTetheringEventCallback.expectUpstreamChanged(NULL_NETWORK);
+        inOrder.verify(mNotificationUpdater).onUpstreamCapabilitiesChanged(null);
     }
 
     @Test
     public void testUpstreamCapabilitiesChanged() {
         final Tethering.TetherMainSM stateMachine = (Tethering.TetherMainSM)
                 mTetheringDependencies.mUpstreamNetworkMonitorSM;
+        final InOrder inOrder = inOrder(mNotificationUpdater);
         final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
         initTetheringUpstream(upstreamState);
+
         stateMachine.chooseUpstreamType(true);
+        inOrder.verify(mNotificationUpdater)
+                .onUpstreamCapabilitiesChanged(upstreamState.networkCapabilities);
 
         stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState);
-        // Should have two onUpstreamCapabilitiesChanged().
-        // One is called by reportUpstreamChanged(). One is called by EVENT_ON_CAPABILITIES.
-        verify(mNotificationUpdater, times(2)).onUpstreamCapabilitiesChanged(any());
-        reset(mNotificationUpdater);
+        inOrder.verify(mNotificationUpdater)
+                .onUpstreamCapabilitiesChanged(upstreamState.networkCapabilities);
+
+        // Verify that onUpstreamCapabilitiesChanged is called if current upstream network
+        // capabilities changed.
+        // Expect that capability is changed with new capability VALIDATED.
+        assertFalse(upstreamState.networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED));
+        upstreamState.networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED);
+        stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState);
+        inOrder.verify(mNotificationUpdater)
+                .onUpstreamCapabilitiesChanged(upstreamState.networkCapabilities);
 
         // Verify that onUpstreamCapabilitiesChanged won't be called if not current upstream network
         // capabilities changed.
@@ -2333,7 +2736,28 @@
                 upstreamState.linkProperties, upstreamState.networkCapabilities,
                 new Network(WIFI_NETID));
         stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState2);
-        verify(mNotificationUpdater, never()).onUpstreamCapabilitiesChanged(any());
+        inOrder.verify(mNotificationUpdater, never()).onUpstreamCapabilitiesChanged(any());
+    }
+
+    @Test
+    public void testUpstreamCapabilitiesChanged_startStopTethering() throws Exception {
+        final TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState());
+
+        // Start USB tethering with no current upstream.
+        prepareUsbTethering();
+        sendUsbBroadcast(true, true, TETHER_USB_RNDIS_FUNCTION);
+
+        // Pretend wifi connected and expect the upstream to be set.
+        wifi.fakeConnect();
+        mCm.makeDefaultNetwork(wifi, CALLBACKS_FIRST);
+        mLooper.dispatchAll();
+        verify(mNotificationUpdater).onUpstreamCapabilitiesChanged(
+                wifi.networkCapabilities);
+
+        // Stop tethering.
+        // Expect that TetherModeAliveState#exit sends capabilities change notification to null.
+        runStopUSBTethering();
+        verify(mNotificationUpdater).onUpstreamCapabilitiesChanged(null);
     }
 
     @Test
@@ -2564,9 +2988,9 @@
         final MacAddress testMac1 = MacAddress.fromString("11:11:11:11:11:11");
         final DhcpLeaseParcelable p2pLease = createDhcpLeaseParcelable("clientId1", testMac1,
                 "192.168.50.24", 24, Long.MAX_VALUE, "test1");
-        final List<TetheredClient> p2pClients = notifyDhcpLeasesChanged(TETHERING_WIFI_P2P,
+        final List<TetheredClient> connectedClients = notifyDhcpLeasesChanged(TETHERING_WIFI_P2P,
                 eventCallbacks, p2pLease);
-        callback.expectTetheredClientChanged(p2pClients);
+        callback.expectTetheredClientChanged(connectedClients);
         reset(mDhcpServer);
 
         // Run wifi tethering.
@@ -2575,21 +2999,11 @@
         verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
                 any(), dhcpEventCbsCaptor.capture());
         eventCallbacks = dhcpEventCbsCaptor.getValue();
-        // Update mac address from softAp callback before getting dhcp lease.
         final MacAddress testMac2 = MacAddress.fromString("22:22:22:22:22:22");
-        final TetheredClient noAddrClient = notifyConnectedWifiClientsChanged(testMac2,
-                false /* isLocalOnly */);
-        final List<TetheredClient> p2pAndNoAddrClients = new ArrayList<>(p2pClients);
-        p2pAndNoAddrClients.add(noAddrClient);
-        callback.expectTetheredClientChanged(p2pAndNoAddrClients);
-
-        // Update dhcp lease for wifi tethering.
         final DhcpLeaseParcelable wifiLease = createDhcpLeaseParcelable("clientId2", testMac2,
                 "192.168.43.24", 24, Long.MAX_VALUE, "test2");
-        final List<TetheredClient> p2pAndWifiClients = new ArrayList<>(p2pClients);
-        p2pAndWifiClients.addAll(notifyDhcpLeasesChanged(TETHERING_WIFI,
-                eventCallbacks, wifiLease));
-        callback.expectTetheredClientChanged(p2pAndWifiClients);
+        verifyHotspotClientUpdate(false /* isLocalOnly */, testMac2, wifiLease, connectedClients,
+                eventCallbacks, callback);
 
         // Test onStarted callback that register second callback when tethering is running.
         TestTetheringEventCallback callback2 = new TestTetheringEventCallback();
@@ -2597,7 +3011,7 @@
             mTethering.registerTetheringEventCallback(callback2);
             mLooper.dispatchAll();
         });
-        callback2.expectTetheredClientChanged(p2pAndWifiClients);
+        callback2.expectTetheredClientChanged(connectedClients);
     }
 
     @Test
@@ -2619,26 +3033,34 @@
         verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
                 any(), dhcpEventCbsCaptor.capture());
         final IDhcpEventCallbacks eventCallbacks = dhcpEventCbsCaptor.getValue();
-        // Update mac address from softAp callback before getting dhcp lease.
-        final MacAddress testMac = MacAddress.fromString("22:22:22:22:22:22");
-        final TetheredClient noAddrClient = notifyConnectedWifiClientsChanged(testMac,
-                true /* isLocalOnly */);
-        final List<TetheredClient> noAddrLocalOnlyClients = new ArrayList<>();
-        noAddrLocalOnlyClients.add(noAddrClient);
-        callback.expectTetheredClientChanged(noAddrLocalOnlyClients);
 
-        // Update dhcp lease for local only hotspot.
+        final List<TetheredClient> connectedClients = new ArrayList<>();
+        final MacAddress testMac = MacAddress.fromString("22:22:22:22:22:22");
         final DhcpLeaseParcelable wifiLease = createDhcpLeaseParcelable("clientId", testMac,
                 "192.168.43.24", 24, Long.MAX_VALUE, "test");
-        final List<TetheredClient> localOnlyClients = notifyDhcpLeasesChanged(TETHERING_WIFI,
-                eventCallbacks, wifiLease);
-        callback.expectTetheredClientChanged(localOnlyClients);
+        verifyHotspotClientUpdate(true /* isLocalOnly */, testMac, wifiLease, connectedClients,
+                eventCallbacks, callback);
 
         // Client disconnect from local only hotspot.
         mLocalOnlyHotspotCallback.onConnectedClientsChanged(Collections.emptyList());
         callback.expectTetheredClientChanged(Collections.emptyList());
     }
 
+    private void verifyHotspotClientUpdate(final boolean isLocalOnly, final MacAddress testMac,
+            final DhcpLeaseParcelable dhcpLease, final List<TetheredClient> currentClients,
+            final IDhcpEventCallbacks dhcpCallback, final TestTetheringEventCallback callback)
+            throws Exception {
+        // Update mac address from softAp callback before getting dhcp lease.
+        final TetheredClient noAddrClient = notifyConnectedWifiClientsChanged(testMac, isLocalOnly);
+        final List<TetheredClient> withNoAddrClients = new ArrayList<>(currentClients);
+        withNoAddrClients.add(noAddrClient);
+        callback.expectTetheredClientChanged(withNoAddrClients);
+
+        // Update dhcp lease for hotspot.
+        currentClients.addAll(notifyDhcpLeasesChanged(TETHERING_WIFI, dhcpCallback, dhcpLease));
+        callback.expectTetheredClientChanged(currentClients);
+    }
+
     private TetheredClient notifyConnectedWifiClientsChanged(final MacAddress mac,
             boolean isLocalOnly) throws Exception {
         final ArrayList<WifiClient> wifiClients = new ArrayList<>();
@@ -2942,6 +3364,7 @@
         verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
                 any(), any());
         verify(mTetheringMetrics).createBuilder(eq(TETHERING_NCM), anyString());
+        verify(mTetheringMetrics, times(1)).maybeUpdateUpstreamType(any());
 
         // Change the USB tethering function to NCM. Because the USB tethering function was set to
         // RNDIS (the default), tethering is stopped.
@@ -2958,6 +3381,7 @@
         mLooper.dispatchAll();
         ncmResult.assertHasResult();
         verify(mTetheringMetrics, times(2)).createBuilder(eq(TETHERING_NCM), anyString());
+        verify(mTetheringMetrics, times(1)).maybeUpdateUpstreamType(any());
         verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_NCM),
                 eq(TETHER_ERROR_SERVICE_UNAVAIL));
         verify(mTetheringMetrics, times(2)).sendReport(eq(TETHERING_NCM));
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
index 9b9507b..e756bd3 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
@@ -49,6 +49,7 @@
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
+import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -62,9 +63,12 @@
 import com.android.net.module.util.SharedLog;
 import com.android.networkstack.tethering.TestConnectivityManager.NetworkRequestInfo;
 import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -92,6 +96,8 @@
     private static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities.Builder()
             .addTransportType(TRANSPORT_WIFI).addCapability(NET_CAPABILITY_INTERNET).build();
 
+    @Rule public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
     @Mock private Context mContext;
     @Mock private EntitlementManager mEntitleMgr;
     @Mock private IConnectivityManager mCS;
@@ -301,6 +307,7 @@
     }
 
     @Test
+    @IgnoreAfter(Build.VERSION_CODES.TIRAMISU)
     public void testSelectPreferredUpstreamType() throws Exception {
         final Collection<Integer> preferredTypes = new ArrayList<>();
         preferredTypes.add(TYPE_WIFI);
@@ -556,6 +563,7 @@
     }
 
     @Test
+    @IgnoreAfter(Build.VERSION_CODES.TIRAMISU)
     public void testSelectMobileWhenMobileIsNotDefault() {
         final Collection<Integer> preferredTypes = new ArrayList<>();
         // Mobile has higher pirority than wifi.
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
index 7fdde97..e2c924c 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
@@ -16,6 +16,12 @@
 
 package com.android.networkstack.tethering.metrics;
 
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_LOWPAN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
 import static android.net.TetheringManager.TETHERING_BLUETOOTH;
 import static android.net.TetheringManager.TETHERING_ETHERNET;
 import static android.net.TetheringManager.TETHERING_NCM;
@@ -44,15 +50,17 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.net.NetworkCapabilities;
 import android.stats.connectivity.DownstreamType;
 import android.stats.connectivity.ErrorCode;
 import android.stats.connectivity.UpstreamType;
 import android.stats.connectivity.UserType;
-import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.networkstack.tethering.UpstreamNetworkState;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -65,138 +73,310 @@
     private static final String SETTINGS_PKG = "com.android.settings";
     private static final String SYSTEMUI_PKG = "com.android.systemui";
     private static final String GMS_PKG = "com.google.android.gms";
-    private TetheringMetrics mTetheringMetrics;
+    private static final long TEST_START_TIME = 1670395936033L;
+    private static final long SECOND_IN_MILLIS = 1_000L;
 
+    private TetheringMetrics mTetheringMetrics;
     private final NetworkTetheringReported.Builder mStatsBuilder =
             NetworkTetheringReported.newBuilder();
 
+    private long mElapsedRealtime;
+
     private class MockTetheringMetrics extends TetheringMetrics {
         @Override
-        public void write(final NetworkTetheringReported reported) { }
+        public void write(final NetworkTetheringReported reported) {}
+        @Override
+        public long timeNow() {
+            return currentTimeMillis();
+        }
+    }
+
+    private long currentTimeMillis() {
+        return TEST_START_TIME + mElapsedRealtime;
+    }
+
+    private void incrementCurrentTime(final long duration) {
+        mElapsedRealtime += duration;
+        mTetheringMetrics.timeNow();
+    }
+
+    private long getElapsedRealtime() {
+        return mElapsedRealtime;
+    }
+
+    private void clearElapsedRealtime() {
+        mElapsedRealtime = 0;
     }
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mTetheringMetrics = spy(new MockTetheringMetrics());
+        mElapsedRealtime = 0L;
     }
 
-    private void verifyReport(DownstreamType downstream, ErrorCode error, UserType user)
+    private void verifyReport(final DownstreamType downstream, final ErrorCode error,
+            final UserType user, final UpstreamEvents.Builder upstreamEvents, final long duration)
             throws Exception {
         final NetworkTetheringReported expectedReport =
                 mStatsBuilder.setDownstreamType(downstream)
                 .setUserType(user)
                 .setUpstreamType(UpstreamType.UT_UNKNOWN)
                 .setErrorCode(error)
-                .setUpstreamEvents(UpstreamEvents.newBuilder())
-                .setDurationMillis(0)
+                .setUpstreamEvents(upstreamEvents)
+                .setDurationMillis(duration)
                 .build();
         verify(mTetheringMetrics).write(expectedReport);
     }
 
-    private void updateErrorAndSendReport(int downstream, int error) {
+    private void updateErrorAndSendReport(final int downstream, final int error) {
         mTetheringMetrics.updateErrorCode(downstream, error);
         mTetheringMetrics.sendReport(downstream);
     }
 
-    private void runDownstreamTypesTest(final Pair<Integer, DownstreamType>... testPairs)
-            throws Exception {
-        for (Pair<Integer, DownstreamType> testPair : testPairs) {
-            final int type = testPair.first;
-            final DownstreamType expectedResult = testPair.second;
-
-            mTetheringMetrics.createBuilder(type, TEST_CALLER_PKG);
-            updateErrorAndSendReport(type, TETHER_ERROR_NO_ERROR);
-            verifyReport(expectedResult, ErrorCode.EC_NO_ERROR, UserType.USER_UNKNOWN);
-            reset(mTetheringMetrics);
+    private static NetworkCapabilities buildUpstreamCapabilities(final int[] transports) {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        for (int type: transports) {
+            nc.addTransportType(type);
         }
+        return nc;
+    }
+
+    private static UpstreamNetworkState buildUpstreamState(final int... transports) {
+        return new UpstreamNetworkState(
+                null,
+                buildUpstreamCapabilities(transports),
+                null);
+    }
+
+    private void addUpstreamEvent(UpstreamEvents.Builder upstreamEvents,
+            final UpstreamType expectedResult, final long duration, final long txBytes,
+                    final long rxBytes) {
+        UpstreamEvent.Builder upstreamEvent = UpstreamEvent.newBuilder()
+                .setUpstreamType(expectedResult)
+                .setDurationMillis(duration)
+                .setTxBytes(txBytes)
+                .setRxBytes(rxBytes);
+        upstreamEvents.addUpstreamEvent(upstreamEvent);
+    }
+
+    private void runDownstreamTypesTest(final int type, final DownstreamType expectedResult)
+            throws Exception {
+        mTetheringMetrics.createBuilder(type, TEST_CALLER_PKG);
+        final long duration = 2 * SECOND_IN_MILLIS;
+        incrementCurrentTime(duration);
+        UpstreamEvents.Builder upstreamEvents = UpstreamEvents.newBuilder();
+        // Set UpstreamType as NO_NETWORK because the upstream type has not been changed.
+        addUpstreamEvent(upstreamEvents, UpstreamType.UT_NO_NETWORK, duration, 0L, 0L);
+        updateErrorAndSendReport(type, TETHER_ERROR_NO_ERROR);
+
+        verifyReport(expectedResult, ErrorCode.EC_NO_ERROR, UserType.USER_UNKNOWN,
+                upstreamEvents, getElapsedRealtime());
+        reset(mTetheringMetrics);
+        clearElapsedRealtime();
+        mTetheringMetrics.cleanup();
     }
 
     @Test
     public void testDownstreamTypes() throws Exception {
-        runDownstreamTypesTest(new Pair<>(TETHERING_WIFI, DownstreamType.DS_TETHERING_WIFI),
-                new Pair<>(TETHERING_WIFI_P2P, DownstreamType.DS_TETHERING_WIFI_P2P),
-                new Pair<>(TETHERING_BLUETOOTH, DownstreamType.DS_TETHERING_BLUETOOTH),
-                new Pair<>(TETHERING_USB, DownstreamType.DS_TETHERING_USB),
-                new Pair<>(TETHERING_NCM, DownstreamType.DS_TETHERING_NCM),
-                new Pair<>(TETHERING_ETHERNET, DownstreamType.DS_TETHERING_ETHERNET));
+        runDownstreamTypesTest(TETHERING_WIFI, DownstreamType.DS_TETHERING_WIFI);
+        runDownstreamTypesTest(TETHERING_WIFI_P2P, DownstreamType.DS_TETHERING_WIFI_P2P);
+        runDownstreamTypesTest(TETHERING_BLUETOOTH, DownstreamType.DS_TETHERING_BLUETOOTH);
+        runDownstreamTypesTest(TETHERING_USB, DownstreamType.DS_TETHERING_USB);
+        runDownstreamTypesTest(TETHERING_NCM, DownstreamType.DS_TETHERING_NCM);
+        runDownstreamTypesTest(TETHERING_ETHERNET, DownstreamType.DS_TETHERING_ETHERNET);
     }
 
-    private void runErrorCodesTest(final Pair<Integer, ErrorCode>... testPairs)
+    private void runErrorCodesTest(final int errorCode, final ErrorCode expectedResult)
             throws Exception {
-        for (Pair<Integer, ErrorCode> testPair : testPairs) {
-            final int errorCode = testPair.first;
-            final ErrorCode expectedResult = testPair.second;
+        mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG);
+        mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_WIFI));
+        final long duration = 2 * SECOND_IN_MILLIS;
+        incrementCurrentTime(duration);
+        updateErrorAndSendReport(TETHERING_WIFI, errorCode);
 
-            mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG);
-            updateErrorAndSendReport(TETHERING_WIFI, errorCode);
-            verifyReport(DownstreamType.DS_TETHERING_WIFI, expectedResult, UserType.USER_UNKNOWN);
-            reset(mTetheringMetrics);
-        }
+        UpstreamEvents.Builder upstreamEvents = UpstreamEvents.newBuilder();
+        addUpstreamEvent(upstreamEvents, UpstreamType.UT_WIFI, duration, 0L, 0L);
+        verifyReport(DownstreamType.DS_TETHERING_WIFI, expectedResult, UserType.USER_UNKNOWN,
+                    upstreamEvents, getElapsedRealtime());
+        reset(mTetheringMetrics);
+        clearElapsedRealtime();
+        mTetheringMetrics.cleanup();
     }
 
     @Test
     public void testErrorCodes() throws Exception {
-        runErrorCodesTest(new Pair<>(TETHER_ERROR_NO_ERROR, ErrorCode.EC_NO_ERROR),
-                new Pair<>(TETHER_ERROR_UNKNOWN_IFACE, ErrorCode.EC_UNKNOWN_IFACE),
-                new Pair<>(TETHER_ERROR_SERVICE_UNAVAIL, ErrorCode.EC_SERVICE_UNAVAIL),
-                new Pair<>(TETHER_ERROR_UNSUPPORTED, ErrorCode.EC_UNSUPPORTED),
-                new Pair<>(TETHER_ERROR_UNAVAIL_IFACE, ErrorCode.EC_UNAVAIL_IFACE),
-                new Pair<>(TETHER_ERROR_INTERNAL_ERROR, ErrorCode.EC_INTERNAL_ERROR),
-                new Pair<>(TETHER_ERROR_TETHER_IFACE_ERROR, ErrorCode.EC_TETHER_IFACE_ERROR),
-                new Pair<>(TETHER_ERROR_UNTETHER_IFACE_ERROR, ErrorCode.EC_UNTETHER_IFACE_ERROR),
-                new Pair<>(TETHER_ERROR_ENABLE_FORWARDING_ERROR,
-                ErrorCode.EC_ENABLE_FORWARDING_ERROR),
-                new Pair<>(TETHER_ERROR_DISABLE_FORWARDING_ERROR,
-                ErrorCode.EC_DISABLE_FORWARDING_ERROR),
-                new Pair<>(TETHER_ERROR_IFACE_CFG_ERROR, ErrorCode.EC_IFACE_CFG_ERROR),
-                new Pair<>(TETHER_ERROR_PROVISIONING_FAILED, ErrorCode.EC_PROVISIONING_FAILED),
-                new Pair<>(TETHER_ERROR_DHCPSERVER_ERROR, ErrorCode.EC_DHCPSERVER_ERROR),
-                new Pair<>(TETHER_ERROR_ENTITLEMENT_UNKNOWN, ErrorCode.EC_ENTITLEMENT_UNKNOWN),
-                new Pair<>(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION,
-                ErrorCode.EC_NO_CHANGE_TETHERING_PERMISSION),
-                new Pair<>(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION,
-                ErrorCode.EC_NO_ACCESS_TETHERING_PERMISSION),
-                new Pair<>(TETHER_ERROR_UNKNOWN_TYPE, ErrorCode.EC_UNKNOWN_TYPE));
+        runErrorCodesTest(TETHER_ERROR_NO_ERROR, ErrorCode.EC_NO_ERROR);
+        runErrorCodesTest(TETHER_ERROR_UNKNOWN_IFACE, ErrorCode.EC_UNKNOWN_IFACE);
+        runErrorCodesTest(TETHER_ERROR_SERVICE_UNAVAIL, ErrorCode.EC_SERVICE_UNAVAIL);
+        runErrorCodesTest(TETHER_ERROR_UNSUPPORTED, ErrorCode.EC_UNSUPPORTED);
+        runErrorCodesTest(TETHER_ERROR_UNAVAIL_IFACE, ErrorCode.EC_UNAVAIL_IFACE);
+        runErrorCodesTest(TETHER_ERROR_INTERNAL_ERROR, ErrorCode.EC_INTERNAL_ERROR);
+        runErrorCodesTest(TETHER_ERROR_TETHER_IFACE_ERROR, ErrorCode.EC_TETHER_IFACE_ERROR);
+        runErrorCodesTest(TETHER_ERROR_UNTETHER_IFACE_ERROR, ErrorCode.EC_UNTETHER_IFACE_ERROR);
+        runErrorCodesTest(TETHER_ERROR_ENABLE_FORWARDING_ERROR,
+                ErrorCode.EC_ENABLE_FORWARDING_ERROR);
+        runErrorCodesTest(TETHER_ERROR_DISABLE_FORWARDING_ERROR,
+                ErrorCode.EC_DISABLE_FORWARDING_ERROR);
+        runErrorCodesTest(TETHER_ERROR_IFACE_CFG_ERROR, ErrorCode.EC_IFACE_CFG_ERROR);
+        runErrorCodesTest(TETHER_ERROR_PROVISIONING_FAILED, ErrorCode.EC_PROVISIONING_FAILED);
+        runErrorCodesTest(TETHER_ERROR_DHCPSERVER_ERROR, ErrorCode.EC_DHCPSERVER_ERROR);
+        runErrorCodesTest(TETHER_ERROR_ENTITLEMENT_UNKNOWN, ErrorCode.EC_ENTITLEMENT_UNKNOWN);
+        runErrorCodesTest(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION,
+                ErrorCode.EC_NO_CHANGE_TETHERING_PERMISSION);
+        runErrorCodesTest(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION,
+                ErrorCode.EC_NO_ACCESS_TETHERING_PERMISSION);
+        runErrorCodesTest(TETHER_ERROR_UNKNOWN_TYPE, ErrorCode.EC_UNKNOWN_TYPE);
     }
 
-    private void runUserTypesTest(final Pair<String, UserType>... testPairs)
+    private void runUserTypesTest(final String callerPkg, final UserType expectedResult)
             throws Exception {
-        for (Pair<String, UserType> testPair : testPairs) {
-            final String callerPkg = testPair.first;
-            final UserType expectedResult = testPair.second;
+        mTetheringMetrics.createBuilder(TETHERING_WIFI, callerPkg);
+        final long duration = 1 * SECOND_IN_MILLIS;
+        incrementCurrentTime(duration);
+        updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_NO_ERROR);
 
-            mTetheringMetrics.createBuilder(TETHERING_WIFI, callerPkg);
-            updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_NO_ERROR);
-            verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR, expectedResult);
-            reset(mTetheringMetrics);
-        }
+        UpstreamEvents.Builder upstreamEvents = UpstreamEvents.newBuilder();
+        // Set UpstreamType as NO_NETWORK because the upstream type has not been changed.
+        addUpstreamEvent(upstreamEvents, UpstreamType.UT_NO_NETWORK, duration, 0L, 0L);
+        verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR, expectedResult,
+                    upstreamEvents, getElapsedRealtime());
+        reset(mTetheringMetrics);
+        clearElapsedRealtime();
+        mTetheringMetrics.cleanup();
     }
 
     @Test
     public void testUserTypes() throws Exception {
-        runUserTypesTest(new Pair<>(TEST_CALLER_PKG, UserType.USER_UNKNOWN),
-                new Pair<>(SETTINGS_PKG, UserType.USER_SETTINGS),
-                new Pair<>(SYSTEMUI_PKG, UserType.USER_SYSTEMUI),
-                new Pair<>(GMS_PKG, UserType.USER_GMS));
+        runUserTypesTest(TEST_CALLER_PKG, UserType.USER_UNKNOWN);
+        runUserTypesTest(SETTINGS_PKG, UserType.USER_SETTINGS);
+        runUserTypesTest(SYSTEMUI_PKG, UserType.USER_SYSTEMUI);
+        runUserTypesTest(GMS_PKG, UserType.USER_GMS);
+    }
+
+    private void runUpstreamTypesTest(final UpstreamNetworkState ns,
+            final UpstreamType expectedResult) throws Exception {
+        mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG);
+        mTetheringMetrics.maybeUpdateUpstreamType(ns);
+        final long duration = 2 * SECOND_IN_MILLIS;
+        incrementCurrentTime(duration);
+        updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_NO_ERROR);
+
+        UpstreamEvents.Builder upstreamEvents = UpstreamEvents.newBuilder();
+        addUpstreamEvent(upstreamEvents, expectedResult, duration, 0L, 0L);
+        verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR,
+                UserType.USER_UNKNOWN, upstreamEvents, getElapsedRealtime());
+        reset(mTetheringMetrics);
+        clearElapsedRealtime();
+        mTetheringMetrics.cleanup();
+    }
+
+    @Test
+    public void testUpstreamTypes() throws Exception {
+        runUpstreamTypesTest(null , UpstreamType.UT_NO_NETWORK);
+        runUpstreamTypesTest(buildUpstreamState(TRANSPORT_CELLULAR), UpstreamType.UT_CELLULAR);
+        runUpstreamTypesTest(buildUpstreamState(TRANSPORT_WIFI), UpstreamType.UT_WIFI);
+        runUpstreamTypesTest(buildUpstreamState(TRANSPORT_BLUETOOTH), UpstreamType.UT_BLUETOOTH);
+        runUpstreamTypesTest(buildUpstreamState(TRANSPORT_ETHERNET), UpstreamType.UT_ETHERNET);
+        runUpstreamTypesTest(buildUpstreamState(TRANSPORT_WIFI_AWARE), UpstreamType.UT_WIFI_AWARE);
+        runUpstreamTypesTest(buildUpstreamState(TRANSPORT_LOWPAN), UpstreamType.UT_LOWPAN);
+        runUpstreamTypesTest(buildUpstreamState(TRANSPORT_CELLULAR, TRANSPORT_WIFI,
+                TRANSPORT_BLUETOOTH), UpstreamType.UT_UNKNOWN);
     }
 
     @Test
     public void testMultiBuildersCreatedBeforeSendReport() throws Exception {
         mTetheringMetrics.createBuilder(TETHERING_WIFI, SETTINGS_PKG);
+        final long wifiTetheringStartTime = currentTimeMillis();
+        incrementCurrentTime(1 * SECOND_IN_MILLIS);
         mTetheringMetrics.createBuilder(TETHERING_USB, SYSTEMUI_PKG);
+        final long usbTetheringStartTime = currentTimeMillis();
+        incrementCurrentTime(2 * SECOND_IN_MILLIS);
         mTetheringMetrics.createBuilder(TETHERING_BLUETOOTH, GMS_PKG);
-
+        final long bluetoothTetheringStartTime = currentTimeMillis();
+        incrementCurrentTime(3 * SECOND_IN_MILLIS);
         updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_DHCPSERVER_ERROR);
+
+        UpstreamEvents.Builder wifiTetheringUpstreamEvents = UpstreamEvents.newBuilder();
+        addUpstreamEvent(wifiTetheringUpstreamEvents, UpstreamType.UT_NO_NETWORK,
+                currentTimeMillis() - wifiTetheringStartTime, 0L, 0L);
         verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_DHCPSERVER_ERROR,
-                UserType.USER_SETTINGS);
-
+                UserType.USER_SETTINGS, wifiTetheringUpstreamEvents,
+                currentTimeMillis() - wifiTetheringStartTime);
+        incrementCurrentTime(1 * SECOND_IN_MILLIS);
         updateErrorAndSendReport(TETHERING_USB, TETHER_ERROR_ENABLE_FORWARDING_ERROR);
-        verifyReport(DownstreamType.DS_TETHERING_USB, ErrorCode.EC_ENABLE_FORWARDING_ERROR,
-                UserType.USER_SYSTEMUI);
 
+        UpstreamEvents.Builder usbTetheringUpstreamEvents = UpstreamEvents.newBuilder();
+        addUpstreamEvent(usbTetheringUpstreamEvents, UpstreamType.UT_NO_NETWORK,
+                currentTimeMillis() - usbTetheringStartTime, 0L, 0L);
+
+        verifyReport(DownstreamType.DS_TETHERING_USB, ErrorCode.EC_ENABLE_FORWARDING_ERROR,
+                UserType.USER_SYSTEMUI, usbTetheringUpstreamEvents,
+                currentTimeMillis() - usbTetheringStartTime);
+        incrementCurrentTime(1 * SECOND_IN_MILLIS);
         updateErrorAndSendReport(TETHERING_BLUETOOTH, TETHER_ERROR_TETHER_IFACE_ERROR);
+
+        UpstreamEvents.Builder bluetoothTetheringUpstreamEvents = UpstreamEvents.newBuilder();
+        addUpstreamEvent(bluetoothTetheringUpstreamEvents, UpstreamType.UT_NO_NETWORK,
+                currentTimeMillis() - bluetoothTetheringStartTime, 0L, 0L);
         verifyReport(DownstreamType.DS_TETHERING_BLUETOOTH, ErrorCode.EC_TETHER_IFACE_ERROR,
-                UserType.USER_GMS);
+                UserType.USER_GMS, bluetoothTetheringUpstreamEvents,
+                currentTimeMillis() - bluetoothTetheringStartTime);
+    }
+
+    @Test
+    public void testUpstreamsWithMultipleDownstreams() throws Exception {
+        mTetheringMetrics.createBuilder(TETHERING_WIFI, SETTINGS_PKG);
+        final long wifiTetheringStartTime = currentTimeMillis();
+        incrementCurrentTime(1 * SECOND_IN_MILLIS);
+        mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_WIFI));
+        final long wifiUpstreamStartTime = currentTimeMillis();
+        incrementCurrentTime(5 * SECOND_IN_MILLIS);
+        mTetheringMetrics.createBuilder(TETHERING_USB, SYSTEMUI_PKG);
+        final long usbTetheringStartTime = currentTimeMillis();
+        incrementCurrentTime(5 * SECOND_IN_MILLIS);
+        updateErrorAndSendReport(TETHERING_USB, TETHER_ERROR_NO_ERROR);
+
+        UpstreamEvents.Builder usbTetheringUpstreamEvents = UpstreamEvents.newBuilder();
+        addUpstreamEvent(usbTetheringUpstreamEvents, UpstreamType.UT_WIFI,
+                currentTimeMillis() - usbTetheringStartTime, 0L, 0L);
+        verifyReport(DownstreamType.DS_TETHERING_USB, ErrorCode.EC_NO_ERROR,
+                UserType.USER_SYSTEMUI, usbTetheringUpstreamEvents,
+                currentTimeMillis() - usbTetheringStartTime);
+        incrementCurrentTime(7 * SECOND_IN_MILLIS);
+        updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_NO_ERROR);
+
+        UpstreamEvents.Builder wifiTetheringUpstreamEvents = UpstreamEvents.newBuilder();
+        addUpstreamEvent(wifiTetheringUpstreamEvents, UpstreamType.UT_WIFI,
+                currentTimeMillis() - wifiUpstreamStartTime, 0L, 0L);
+        verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR,
+                UserType.USER_SETTINGS, wifiTetheringUpstreamEvents,
+                currentTimeMillis() - wifiTetheringStartTime);
+    }
+
+    @Test
+    public void testSwitchingMultiUpstreams() throws Exception {
+        mTetheringMetrics.createBuilder(TETHERING_WIFI, SETTINGS_PKG);
+        final long wifiTetheringStartTime = currentTimeMillis();
+        incrementCurrentTime(1 * SECOND_IN_MILLIS);
+        mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_WIFI));
+        final long wifiDuration = 5 * SECOND_IN_MILLIS;
+        incrementCurrentTime(wifiDuration);
+        mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_BLUETOOTH));
+        final long bluetoothDuration = 15 * SECOND_IN_MILLIS;
+        incrementCurrentTime(bluetoothDuration);
+        mTetheringMetrics.maybeUpdateUpstreamType(buildUpstreamState(TRANSPORT_CELLULAR));
+        final long celltoothDuration = 20 * SECOND_IN_MILLIS;
+        incrementCurrentTime(celltoothDuration);
+        updateErrorAndSendReport(TETHERING_WIFI, TETHER_ERROR_NO_ERROR);
+
+        UpstreamEvents.Builder upstreamEvents = UpstreamEvents.newBuilder();
+        addUpstreamEvent(upstreamEvents, UpstreamType.UT_WIFI, wifiDuration, 0L, 0L);
+        addUpstreamEvent(upstreamEvents, UpstreamType.UT_BLUETOOTH, bluetoothDuration, 0L, 0L);
+        addUpstreamEvent(upstreamEvents, UpstreamType.UT_CELLULAR, celltoothDuration, 0L, 0L);
+
+        verifyReport(DownstreamType.DS_TETHERING_WIFI, ErrorCode.EC_NO_ERROR,
+                UserType.USER_SETTINGS, upstreamEvents,
+                currentTimeMillis() - wifiTetheringStartTime);
     }
 }
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 8eb9cfd..b3f8ed6 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -54,7 +54,6 @@
         "//packages/modules/Connectivity/tests/native/utilities",
         "//packages/modules/Connectivity/service-t/native/libs/libnetworkstats",
         "//packages/modules/Connectivity/tests/unit/jni",
-        "//system/netd/tests",
     ],
 }
 
@@ -104,18 +103,6 @@
 }
 
 bpf {
-    name: "offload@inprocess.o",
-    srcs: ["offload@inprocess.c"],
-    btf: true,
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-DBTF",
-        "-DINPROCESS",
-    ],
-}
-
-bpf {
     name: "test.o",
     srcs: ["test.c"],
     cflags: [
@@ -136,18 +123,6 @@
 }
 
 bpf {
-    name: "test@inprocess.o",
-    srcs: ["test@inprocess.c"],
-    btf: true,
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-DBTF",
-        "-DINPROCESS",
-    ],
-}
-
-bpf {
     name: "clatd.o",
     srcs: ["clatd.c"],
     btf: true,
diff --git a/bpf_progs/block.c b/bpf_progs/block.c
index f2a3e62..3797a38 100644
--- a/bpf_progs/block.c
+++ b/bpf_progs/block.c
@@ -19,8 +19,8 @@
 #include <netinet/in.h>
 #include <stdint.h>
 
-// The resulting .o needs to load on the Android T beta 3 bpfloader
-#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+// The resulting .o needs to load on the Android T bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
 
 #include "bpf_helpers.h"
 
diff --git a/bpf_progs/bpf_net_helpers.h b/bpf_progs/bpf_net_helpers.h
index c39269e..ed33cc9 100644
--- a/bpf_progs/bpf_net_helpers.h
+++ b/bpf_progs/bpf_net_helpers.h
@@ -21,6 +21,18 @@
 #include <stdbool.h>
 #include <stdint.h>
 
+// bionic kernel uapi linux/udp.h header is munged...
+#define __kernel_udphdr udphdr
+#include <linux/udp.h>
+
+// Offsets from beginning of L4 (TCP/UDP) header
+#define TCP_OFFSET(field) offsetof(struct tcphdr, field)
+#define UDP_OFFSET(field) offsetof(struct udphdr, field)
+
+// Offsets from beginning of L3 (IPv4/IPv6) header
+#define IP4_OFFSET(field) offsetof(struct iphdr, field)
+#define IP6_OFFSET(field) offsetof(struct ipv6hdr, field)
+
 // this returns 0 iff skb->sk is NULL
 static uint64_t (*bpf_get_socket_cookie)(struct __sk_buff* skb) = (void*)BPF_FUNC_get_socket_cookie;
 
@@ -74,3 +86,30 @@
     if (len > skb->len) len = skb->len;
     if (skb->data_end - skb->data < len) bpf_skb_pull_data(skb, len);
 }
+
+// constants for passing in to 'bool egress'
+static const bool INGRESS = false;
+static const bool EGRESS = true;
+
+// constants for passing in to 'bool downstream'
+static const bool UPSTREAM = false;
+static const bool DOWNSTREAM = true;
+
+// constants for passing in to 'bool is_ethernet'
+static const bool RAWIP = false;
+static const bool ETHER = true;
+
+// constants for passing in to 'bool updatetime'
+static const bool NO_UPDATETIME = false;
+static const bool UPDATETIME = true;
+
+// constants for passing in to ignore_on_eng / ignore_on_user / ignore_on_userdebug
+// define's instead of static const due to tm-mainline-prod compiler static_assert limitations
+#define LOAD_ON_ENG false
+#define LOAD_ON_USER false
+#define LOAD_ON_USERDEBUG false
+#define IGNORE_ON_ENG true
+#define IGNORE_ON_USER true
+#define IGNORE_ON_USERDEBUG true
+
+#define KVER_4_14 KVER(4, 14, 0)
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index 14cddf6..85ba58e 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -30,8 +30,8 @@
 #define __kernel_udphdr udphdr
 #include <linux/udp.h>
 
-// The resulting .o needs to load on the Android T beta 3 bpfloader
-#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+// The resulting .o needs to load on the Android T bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
 
 #include "bpf_helpers.h"
 #include "bpf_net_helpers.h"
@@ -54,7 +54,9 @@
 
 DEFINE_BPF_MAP_GRW(clat_ingress6_map, HASH, ClatIngress6Key, ClatIngress6Value, 16, AID_SYSTEM)
 
-static inline __always_inline int nat64(struct __sk_buff* skb, bool is_ethernet) {
+static inline __always_inline int nat64(struct __sk_buff* skb,
+                                        const bool is_ethernet,
+                                        const unsigned kver) {
     // Require ethernet dst mac address to be our unicast address.
     if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE;
 
@@ -83,6 +85,12 @@
     if (ip6->version != 6) return TC_ACT_PIPE;
 
     // Maximum IPv6 payload length that can be translated to IPv4
+    // Note: technically this check is too strict for an IPv6 fragment,
+    // which by virtue of stripping the extra 8 byte fragment extension header,
+    // could thus be 8 bytes larger and still fit in an ipv4 packet post
+    // translation.  However... who ever heard of receiving ~64KB frags...
+    // fragments are kind of by definition smaller than ingress device mtu,
+    // and thus, on the internet, very very unlikely to exceed 1500 bytes.
     if (ntohs(ip6->payload_len) > 0xFFFF - sizeof(struct iphdr)) return TC_ACT_PIPE;
 
     ClatIngress6Key k = {
@@ -106,6 +114,9 @@
     __u16 tot_len = ntohs(ip6->payload_len) + sizeof(struct iphdr);  // cannot overflow, see above
 
     if (proto == IPPROTO_FRAGMENT) {
+        // Fragment handling requires bpf_skb_adjust_room which is 4.14+
+        if (kver < KVER_4_14) return TC_ACT_PIPE;
+
         // Must have (ethernet and) ipv6 header and ipv6 fragment extension header
         if (data + l2_header_size + sizeof(*ip6) + sizeof(struct frag_hdr) > data_end)
             return TC_ACT_PIPE;
@@ -208,7 +219,20 @@
     //   return -ENOTSUPP;
     bpf_csum_update(skb, sum6);
 
-    if (frag_off != htons(IP_DF)) {
+    // Technically 'kver < KVER_4_14' already implies 'frag_off == htons(IP_DF)' due to logic above,
+    // thus the initial 'kver >= KVER_4_14' check here is entirely superfluous.
+    //
+    // However, we *need* the compiler (when compiling the program for 4.9) to entirely
+    // optimize out the call to bpf_skb_adjust_room() bpf helper: it's not enough for it to emit
+    // an unreachable call to it, it must *not* emit it at all (otherwise the 4.9 kernel's
+    // bpf verifier will refuse to load a program with an unknown bpf helper call)
+    //
+    // This is easiest to achieve by being very explicit in the if clause,
+    // better safe than sorry...
+    //
+    // Note: we currently have no TreeHugger coverage for 4.9-T devices (there are no such
+    // Pixel or cuttlefish devices), so likely you won't notice for months if this breaks...
+    if (kver >= KVER_4_14 && frag_off != htons(IP_DF)) {
         // If we're converting an IPv6 Fragment, we need to trim off 8 more bytes
         // We're beyond recovery on error here... but hard to imagine how this could fail.
         if (bpf_skb_adjust_room(skb, -(__s32)sizeof(struct frag_hdr), BPF_ADJ_ROOM_NET, /*flags*/0))
@@ -243,14 +267,24 @@
     return TC_ACT_PIPE;
 }
 
-DEFINE_BPF_PROG("schedcls/ingress6/clat_ether", AID_ROOT, AID_SYSTEM, sched_cls_ingress6_clat_ether)
+DEFINE_BPF_PROG_KVER("schedcls/ingress6/clat_ether$4_14", AID_ROOT, AID_SYSTEM, sched_cls_ingress6_clat_ether_4_14, KVER_4_14)
 (struct __sk_buff* skb) {
-    return nat64(skb, true);
+    return nat64(skb, ETHER, KVER_4_14);
 }
 
-DEFINE_BPF_PROG("schedcls/ingress6/clat_rawip", AID_ROOT, AID_SYSTEM, sched_cls_ingress6_clat_rawip)
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/ingress6/clat_ether$4_9", AID_ROOT, AID_SYSTEM, sched_cls_ingress6_clat_ether_4_9, KVER_NONE, KVER_4_14)
 (struct __sk_buff* skb) {
-    return nat64(skb, false);
+    return nat64(skb, ETHER, KVER_NONE);
+}
+
+DEFINE_BPF_PROG_KVER("schedcls/ingress6/clat_rawip$4_14", AID_ROOT, AID_SYSTEM, sched_cls_ingress6_clat_rawip_4_14, KVER_4_14)
+(struct __sk_buff* skb) {
+    return nat64(skb, RAWIP, KVER_4_14);
+}
+
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/ingress6/clat_rawip$4_9", AID_ROOT, AID_SYSTEM, sched_cls_ingress6_clat_rawip_4_9, KVER_NONE, KVER_4_14)
+(struct __sk_buff* skb) {
+    return nat64(skb, RAWIP, KVER_NONE);
 }
 
 DEFINE_BPF_MAP_GRW(clat_egress4_map, HASH, ClatEgress4Key, ClatEgress4Value, 16, AID_SYSTEM)
diff --git a/bpf_progs/dscpPolicy.c b/bpf_progs/dscpPolicy.c
index 72f63c6..262b65b 100644
--- a/bpf_progs/dscpPolicy.c
+++ b/bpf_progs/dscpPolicy.c
@@ -27,8 +27,8 @@
 #include <stdint.h>
 #include <string.h>
 
-// The resulting .o needs to load on the Android T beta 3 bpfloader
-#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+// The resulting .o needs to load on the Android T bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
 
 #include "bpf_helpers.h"
 #include "dscpPolicy.h"
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 8e99b8d..245ad7a 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-// The resulting .o needs to load on the Android T Beta 3 bpfloader
-#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION
+// The resulting .o needs to load on the Android T bpfloader
+#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
 
 #include <bpf_helpers.h>
 #include <linux/bpf.h>
@@ -31,7 +31,7 @@
 #include <stdbool.h>
 #include <stdint.h>
 #include "bpf_net_helpers.h"
-#include "bpf_shared.h"
+#include "netd.h"
 
 // This is defined for cgroup bpf filter only.
 static const int DROP = 0;
@@ -42,12 +42,9 @@
 static const int BPF_NOMATCH = 0;
 static const int BPF_MATCH = 1;
 
-// Used for 'bool egress'
-static const bool INGRESS = false;
-static const bool EGRESS = true;
-
-#define IP_PROTO_OFF offsetof(struct iphdr, protocol)
-#define IPV6_PROTO_OFF offsetof(struct ipv6hdr, nexthdr)
+// Used for 'bool enable_tracing'
+static const bool TRACE_ON = true;
+static const bool TRACE_OFF = false;
 
 // offsetof(struct iphdr, ihl) -- but that's a bitfield
 #define IPPROTO_IHL_OFF 0
@@ -60,14 +57,18 @@
 #define TCP_FLAG32_OFF 12
 
 // For maps netd does not need to access
-#define DEFINE_BPF_MAP_NO_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \
-    DEFINE_BPF_MAP_EXT(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, \
-                       AID_ROOT, AID_NET_BW_ACCT, 0060, "fs_bpf_net_shared", "", false)
+#define DEFINE_BPF_MAP_NO_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries)      \
+    DEFINE_BPF_MAP_EXT(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries,              \
+                       AID_ROOT, AID_NET_BW_ACCT, 0060, "fs_bpf_net_shared", "", false, \
+                       BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, LOAD_ON_ENG,       \
+                       LOAD_ON_USER, LOAD_ON_USERDEBUG)
 
 // For maps netd only needs read only access to
-#define DEFINE_BPF_MAP_RO_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \
-    DEFINE_BPF_MAP_EXT(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, \
-                       AID_ROOT, AID_NET_BW_ACCT, 0460, "fs_bpf_netd_readonly", "", false)
+#define DEFINE_BPF_MAP_RO_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries)         \
+    DEFINE_BPF_MAP_EXT(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries,                 \
+                       AID_ROOT, AID_NET_BW_ACCT, 0460, "fs_bpf_netd_readonly", "", false, \
+                       BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, LOAD_ON_ENG,       \
+                       LOAD_ON_USER, LOAD_ON_USERDEBUG)
 
 // For maps netd needs to be able to read and write
 #define DEFINE_BPF_MAP_RW_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \
@@ -95,6 +96,19 @@
 /* never actually used from ebpf */
 DEFINE_BPF_MAP_NO_NETD(iface_index_name_map, HASH, uint32_t, IfaceValue, IFACE_INDEX_NAME_MAP_SIZE)
 
+// A single-element configuration array, packet tracing is enabled when 'true'.
+DEFINE_BPF_MAP_EXT(packet_trace_enabled_map, ARRAY, uint32_t, bool, 1,
+                   AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", false,
+                   BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
+                   IGNORE_ON_USER, LOAD_ON_USERDEBUG)
+
+// A ring buffer on which packet information is pushed. This map will only be loaded
+// on eng and userdebug devices. User devices won't load this to save memory.
+DEFINE_BPF_RINGBUF_EXT(packet_trace_ringbuf, PacketTrace, PACKET_TRACE_BUF_SIZE,
+                       AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", false,
+                       BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
+                       IGNORE_ON_USER, LOAD_ON_USERDEBUG);
+
 // iptables xt_bpf programs need to be usable by both netd and netutils_wrappers
 // selinux contexts, because even non-xt_bpf iptables mutations are implemented as
 // a full table dump, followed by an update in userspace, and then a reload into the kernel,
@@ -109,8 +123,9 @@
 // (this is because these are currently attached by the mainline provided libnetd_updatable .so
 // which is loaded into netd and thus runs as netd uid/gid/selinux context)
 #define DEFINE_NETD_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, minKV, maxKV) \
-    DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, \
-                        minKV, maxKV, false, "fs_bpf_netd_readonly", "")
+    DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog,                               \
+                        minKV, maxKV, BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, false,                \
+                        "fs_bpf_netd_readonly", "", false, false, false)
 
 #define DEFINE_NETD_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv) \
     DEFINE_NETD_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, KVER_INF)
@@ -120,8 +135,9 @@
 
 // programs that only need to be usable by the system server
 #define DEFINE_SYS_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
-    DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, \
-                        KVER_NONE, KVER_INF, false, "fs_bpf_net_shared", "")
+    DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, KVER_NONE, KVER_INF,  \
+                        BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, false, "fs_bpf_net_shared", \
+                        "", false, false, false)
 
 static __always_inline int is_system_uid(uint32_t uid) {
     // MIN_SYSTEM_UID is AID_ROOT == 0, so uint32_t is *always* >= 0
@@ -156,36 +172,38 @@
  * Especially since the number of packets is important for any future clat offload correction.
  * (which adjusts upward by 20 bytes per packet to account for ipv4 -> ipv6 header conversion)
  */
-#define DEFINE_UPDATE_STATS(the_stats_map, TypeOfKey)                                          \
-    static __always_inline inline void update_##the_stats_map(struct __sk_buff* skb,           \
-                                                              bool egress, TypeOfKey* key) {   \
-        StatsValue* value = bpf_##the_stats_map##_lookup_elem(key);                            \
-        if (!value) {                                                                          \
-            StatsValue newValue = {};                                                          \
-            bpf_##the_stats_map##_update_elem(key, &newValue, BPF_NOEXIST);                    \
-            value = bpf_##the_stats_map##_lookup_elem(key);                                    \
-        }                                                                                      \
-        if (value) {                                                                           \
-            const int mtu = 1500;                                                              \
-            uint64_t packets = 1;                                                              \
-            uint64_t bytes = skb->len;                                                         \
-            if (bytes > mtu) {                                                                 \
-                bool is_ipv6 = (skb->protocol == htons(ETH_P_IPV6));                           \
-                int ip_overhead = (is_ipv6 ? sizeof(struct ipv6hdr) : sizeof(struct iphdr));   \
-                int tcp_overhead = ip_overhead + sizeof(struct tcphdr) + 12;                   \
-                int mss = mtu - tcp_overhead;                                                  \
-                uint64_t payload = bytes - tcp_overhead;                                       \
-                packets = (payload + mss - 1) / mss;                                           \
-                bytes = tcp_overhead * packets + payload;                                      \
-            }                                                                                  \
-            if (egress) {                                                                      \
-                __sync_fetch_and_add(&value->txPackets, packets);                              \
-                __sync_fetch_and_add(&value->txBytes, bytes);                                  \
-            } else {                                                                           \
-                __sync_fetch_and_add(&value->rxPackets, packets);                              \
-                __sync_fetch_and_add(&value->rxBytes, bytes);                                  \
-            }                                                                                  \
-        }                                                                                      \
+#define DEFINE_UPDATE_STATS(the_stats_map, TypeOfKey)                                            \
+    static __always_inline inline void update_##the_stats_map(const struct __sk_buff* const skb, \
+                                                              const TypeOfKey* const key,        \
+                                                              const bool egress,                 \
+                                                              const unsigned kver) {             \
+        StatsValue* value = bpf_##the_stats_map##_lookup_elem(key);                              \
+        if (!value) {                                                                            \
+            StatsValue newValue = {};                                                            \
+            bpf_##the_stats_map##_update_elem(key, &newValue, BPF_NOEXIST);                      \
+            value = bpf_##the_stats_map##_lookup_elem(key);                                      \
+        }                                                                                        \
+        if (value) {                                                                             \
+            const int mtu = 1500;                                                                \
+            uint64_t packets = 1;                                                                \
+            uint64_t bytes = skb->len;                                                           \
+            if (bytes > mtu) {                                                                   \
+                bool is_ipv6 = (skb->protocol == htons(ETH_P_IPV6));                             \
+                int ip_overhead = (is_ipv6 ? sizeof(struct ipv6hdr) : sizeof(struct iphdr));     \
+                int tcp_overhead = ip_overhead + sizeof(struct tcphdr) + 12;                     \
+                int mss = mtu - tcp_overhead;                                                    \
+                uint64_t payload = bytes - tcp_overhead;                                         \
+                packets = (payload + mss - 1) / mss;                                             \
+                bytes = tcp_overhead * packets + payload;                                        \
+            }                                                                                    \
+            if (egress) {                                                                        \
+                __sync_fetch_and_add(&value->txPackets, packets);                                \
+                __sync_fetch_and_add(&value->txBytes, bytes);                                    \
+            } else {                                                                             \
+                __sync_fetch_and_add(&value->rxPackets, packets);                                \
+                __sync_fetch_and_add(&value->rxBytes, bytes);                                    \
+            }                                                                                    \
+        }                                                                                        \
     }
 
 DEFINE_UPDATE_STATS(app_uid_stats_map, uint32_t)
@@ -194,19 +212,99 @@
 DEFINE_UPDATE_STATS(stats_map_B, StatsKey)
 
 // both of these return 0 on success or -EFAULT on failure (and zero out the buffer)
-static __always_inline inline int bpf_skb_load_bytes_net(const struct __sk_buff* skb, int off,
-                                                         void* to, int len, bool is_4_19) {
-    return is_4_19
-        ? bpf_skb_load_bytes_relative(skb, off, to, len, BPF_HDR_START_NET)
-        : bpf_skb_load_bytes(skb, off, to, len);
+static __always_inline inline int bpf_skb_load_bytes_net(const struct __sk_buff* const skb,
+                                                         const int L3_off,
+                                                         void* const to,
+                                                         const int len,
+                                                         const unsigned kver) {
+    // 'kver' (here and throughout) is the compile time guaranteed minimum kernel version,
+    // ie. we're building (a version of) the bpf program for kver (or newer!) kernels.
+    //
+    // 4.19+ kernels support the 'bpf_skb_load_bytes_relative()' bpf helper function,
+    // so we can use it.  On pre-4.19 kernels we cannot use the relative load helper,
+    // and thus will simply get things wrong if there's any L2 (ethernet) header in the skb.
+    //
+    // Luckily, for cellular traffic, there likely isn't any, as cell is usually 'rawip'.
+    //
+    // However, this does mean that wifi (and ethernet) on 4.14 is basically a lost cause:
+    // we'll be making decisions based on the *wrong* bytes (fetched from the wrong offset),
+    // because the 'L3_off' passed to bpf_skb_load_bytes() should be increased by l2_header_size,
+    // which for ethernet is 14 and not 0 like it is for rawip.
+    //
+    // For similar reasons this will fail with non-offloaded VLAN tags on < 4.19 kernels,
+    // since those extend the ethernet header from 14 to 18 bytes.
+    return kver >= KVER(4, 19, 0)
+        ? bpf_skb_load_bytes_relative(skb, L3_off, to, len, BPF_HDR_START_NET)
+        : bpf_skb_load_bytes(skb, L3_off, to, len);
 }
 
-static __always_inline inline bool skip_owner_match(struct __sk_buff* skb, bool is_4_19) {
+static __always_inline inline void do_packet_tracing(
+        const struct __sk_buff* const skb, const bool egress, const uint32_t uid,
+        const uint32_t tag, const bool enable_tracing, const unsigned kver) {
+    if (!enable_tracing) return;
+    if (kver < KVER(5, 8, 0)) return;
+
+    uint32_t mapKey = 0;
+    bool* traceConfig = bpf_packet_trace_enabled_map_lookup_elem(&mapKey);
+    if (traceConfig == NULL) return;
+    if (*traceConfig == false) return;
+
+    PacketTrace* pkt = bpf_packet_trace_ringbuf_reserve();
+    if (pkt == NULL) return;
+
+    // Errors from bpf_skb_load_bytes_net are ignored to favor returning something
+    // over returning nothing. In the event of an error, the kernel will fill in
+    // zero for the destination memory. Do not change the default '= 0' below.
+
+    uint8_t proto = 0;
+    uint8_t L4_off = 0;
+    uint8_t ipVersion = 0;
+    if (skb->protocol == htons(ETH_P_IP)) {
+        (void)bpf_skb_load_bytes_net(skb, IP4_OFFSET(protocol), &proto, sizeof(proto), kver);
+        (void)bpf_skb_load_bytes_net(skb, IPPROTO_IHL_OFF, &L4_off, sizeof(L4_off), kver);
+        L4_off = (L4_off & 0x0F) * 4;  // IHL calculation.
+        ipVersion = 4;
+    } else if (skb->protocol == htons(ETH_P_IPV6)) {
+        (void)bpf_skb_load_bytes_net(skb, IP6_OFFSET(nexthdr), &proto, sizeof(proto), kver);
+        L4_off = sizeof(struct ipv6hdr);
+        ipVersion = 6;
+    }
+
+    uint8_t flags = 0;
+    __be16 sport = 0, dport = 0;
+    if (proto == IPPROTO_TCP && L4_off >= 20) {
+        (void)bpf_skb_load_bytes_net(skb, L4_off + TCP_FLAG32_OFF + 1, &flags, sizeof(flags), kver);
+        (void)bpf_skb_load_bytes_net(skb, L4_off + TCP_OFFSET(source), &sport, sizeof(sport), kver);
+        (void)bpf_skb_load_bytes_net(skb, L4_off + TCP_OFFSET(dest), &dport, sizeof(dport), kver);
+    } else if (proto == IPPROTO_UDP && L4_off >= 20) {
+        (void)bpf_skb_load_bytes_net(skb, L4_off + UDP_OFFSET(source), &sport, sizeof(sport), kver);
+        (void)bpf_skb_load_bytes_net(skb, L4_off + UDP_OFFSET(dest), &dport, sizeof(dport), kver);
+    }
+
+    pkt->timestampNs = bpf_ktime_get_boot_ns();
+    pkt->ifindex = skb->ifindex;
+    pkt->length = skb->len;
+
+    pkt->uid = uid;
+    pkt->tag = tag;
+    pkt->sport = sport;
+    pkt->dport = dport;
+
+    pkt->egress = egress;
+    pkt->ipProto = proto;
+    pkt->tcpFlags = flags;
+    pkt->ipVersion = ipVersion;
+
+    bpf_packet_trace_ringbuf_submit(pkt);
+}
+
+static __always_inline inline bool skip_owner_match(struct __sk_buff* skb, bool egress,
+                                                    const unsigned kver) {
     uint32_t flag = 0;
     if (skb->protocol == htons(ETH_P_IP)) {
         uint8_t proto;
         // no need to check for success, proto will be zeroed if bpf_skb_load_bytes_net() fails
-        (void)bpf_skb_load_bytes_net(skb, IP_PROTO_OFF, &proto, sizeof(proto), is_4_19);
+        (void)bpf_skb_load_bytes_net(skb, IP4_OFFSET(protocol), &proto, sizeof(proto), kver);
         if (proto == IPPROTO_ESP) return true;
         if (proto != IPPROTO_TCP) return false;  // handles read failure above
         uint8_t ihl;
@@ -215,23 +313,24 @@
         // (a little bit deeper in the packet in spite of ihl being zeroed) of the tcp flags
         // field will also fail, and that failure we already handle correctly
         // (we also don't check that ihl in [0x45,0x4F] nor that ipv4 header checksum is correct)
-        (void)bpf_skb_load_bytes_net(skb, IPPROTO_IHL_OFF, &ihl, sizeof(ihl), is_4_19);
+        (void)bpf_skb_load_bytes_net(skb, IPPROTO_IHL_OFF, &ihl, sizeof(ihl), kver);
         // if the read below fails, we'll just assume no TCP flags are set, which is fine.
         (void)bpf_skb_load_bytes_net(skb, (ihl & 0xF) * 4 + TCP_FLAG32_OFF,
-                                     &flag, sizeof(flag), is_4_19);
+                                     &flag, sizeof(flag), kver);
     } else if (skb->protocol == htons(ETH_P_IPV6)) {
         uint8_t proto;
         // no need to check for success, proto will be zeroed if bpf_skb_load_bytes_net() fails
-        (void)bpf_skb_load_bytes_net(skb, IPV6_PROTO_OFF, &proto, sizeof(proto), is_4_19);
+        (void)bpf_skb_load_bytes_net(skb, IP6_OFFSET(nexthdr), &proto, sizeof(proto), kver);
         if (proto == IPPROTO_ESP) return true;
         if (proto != IPPROTO_TCP) return false;  // handles read failure above
         // if the read below fails, we'll just assume no TCP flags are set, which is fine.
         (void)bpf_skb_load_bytes_net(skb, sizeof(struct ipv6hdr) + TCP_FLAG32_OFF,
-                                     &flag, sizeof(flag), is_4_19);
+                                     &flag, sizeof(flag), kver);
     } else {
         return false;
     }
-    return flag & TCP_FLAG_RST;  // false on read failure
+    // Always allow RST's, and additionally allow ingress FINs
+    return flag & (TCP_FLAG_RST | (egress ? 0 : TCP_FLAG_FIN));  // false on read failure
 }
 
 static __always_inline inline BpfConfig getConfig(uint32_t configKey) {
@@ -250,11 +349,11 @@
 #define DROP_IF_UNSET (DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH | LOW_POWER_STANDBY_MATCH)
 
 static __always_inline inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid,
-                                                  bool egress, bool is_4_19) {
-    if (skip_owner_match(skb, is_4_19)) return PASS;
-
+                                                  bool egress, const unsigned kver) {
     if (is_system_uid(uid)) return PASS;
 
+    if (skip_owner_match(skb, egress, kver)) return PASS;
+
     BpfConfig enabledRules = getConfig(UID_RULES_CONFIGURATION_KEY);
 
     UidOwnerValue* uidEntry = bpf_uid_owner_map_lookup_elem(&uid);
@@ -284,17 +383,21 @@
     return PASS;
 }
 
-static __always_inline inline void update_stats_with_config(struct __sk_buff* skb, bool egress,
-                                                            StatsKey* key, uint32_t selectedMap) {
+static __always_inline inline void update_stats_with_config(const uint32_t selectedMap,
+                                                            const struct __sk_buff* const skb,
+                                                            const StatsKey* const key,
+                                                            const bool egress,
+                                                            const unsigned kver) {
     if (selectedMap == SELECT_MAP_A) {
-        update_stats_map_A(skb, egress, key);
-    } else if (selectedMap == SELECT_MAP_B) {
-        update_stats_map_B(skb, egress, key);
+        update_stats_map_A(skb, key, egress, kver);
+    } else {
+        update_stats_map_B(skb, key, egress, kver);
     }
 }
 
 static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb, bool egress,
-                                                      bool is_4_19) {
+                                                      const bool enable_tracing,
+                                                      const unsigned kver) {
     uint32_t sock_uid = bpf_get_socket_uid(skb);
     uint64_t cookie = bpf_get_socket_cookie(skb);
     UidTagValue* utag = bpf_cookie_tag_map_lookup_elem(&cookie);
@@ -309,17 +412,10 @@
 
     // Always allow and never count clat traffic. Only the IPv4 traffic on the stacked
     // interface is accounted for and subject to usage restrictions.
-    // TODO: remove sock_uid check once Nat464Xlat javaland adds the socket tag AID_CLAT for clat.
-    if (sock_uid == AID_CLAT || uid == AID_CLAT) {
-        return PASS;
-    }
+    // CLAT IPv6 TX sockets are *always* tagged with CLAT uid, see tagSocketAsClat()
+    if (uid == AID_CLAT) return PASS;
 
-    int match = bpf_owner_match(skb, sock_uid, egress, is_4_19);
-    if (egress && (match == DROP)) {
-        // If an outbound packet is going to be dropped, we do not count that
-        // traffic.
-        return match;
-    }
+    int match = bpf_owner_match(skb, sock_uid, egress, kver);
 
 // Workaround for secureVPN with VpnIsolation enabled, refer to b/159994981 for details.
 // Keep TAG_SYSTEM_DNS in sync with DnsResolver/include/netd_resolv/resolv.h
@@ -332,6 +428,9 @@
         if (match == DROP_UNLESS_DNS) match = DROP;
     }
 
+    // If an outbound packet is going to be dropped, we do not count that traffic.
+    if (egress && (match == DROP)) return DROP;
+
     StatsKey key = {.uid = uid, .tag = tag, .counterSet = 0, .ifaceIndex = skb->ifindex};
 
     uint8_t* counterSet = bpf_uid_counterset_map_lookup_elem(&uid);
@@ -340,47 +439,59 @@
     uint32_t mapSettingKey = CURRENT_STATS_MAP_CONFIGURATION_KEY;
     uint32_t* selectedMap = bpf_configuration_map_lookup_elem(&mapSettingKey);
 
-    // Use asm("%0 &= 1" : "+r"(match)) before return match,
-    // to help kernel's bpf verifier, so that it can be 100% certain
-    // that the returned value is always BPF_NOMATCH(0) or BPF_MATCH(1).
-    if (!selectedMap) {
-        asm("%0 &= 1" : "+r"(match));
-        return match;
-    }
+    if (!selectedMap) return PASS;  // cannot happen, needed to keep bpf verifier happy
 
-    if (key.tag) {
-        update_stats_with_config(skb, egress, &key, *selectedMap);
-        key.tag = 0;
-    }
+    do_packet_tracing(skb, egress, uid, tag, enable_tracing, kver);
+    update_stats_with_config(*selectedMap, skb, &key, egress, kver);
+    update_app_uid_stats_map(skb, &uid, egress, kver);
 
-    update_stats_with_config(skb, egress, &key, *selectedMap);
-    update_app_uid_stats_map(skb, egress, &uid);
+    // We've already handled DROP_UNLESS_DNS up above, thus when we reach here the only
+    // possible values of match are DROP(0) or PASS(1), however we need to use
+    // "match &= 1" before 'return match' to help the kernel's bpf verifier,
+    // so that it can be 100% certain that the returned value is always 0 or 1.
+    // We use assembly so that it cannot be optimized out by a too smart compiler.
     asm("%0 &= 1" : "+r"(match));
     return match;
 }
 
+DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace", AID_ROOT, AID_SYSTEM,
+                    bpf_cgroup_ingress_trace, KVER(5, 8, 0), KVER_INF,
+                    BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, false,
+                    "fs_bpf_netd_readonly", "", false, true, false)
+(struct __sk_buff* skb) {
+    return bpf_traffic_account(skb, INGRESS, TRACE_ON, KVER(5, 8, 0));
+}
+
 DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/ingress/stats$4_19", AID_ROOT, AID_SYSTEM,
                                 bpf_cgroup_ingress_4_19, KVER(4, 19, 0), KVER_INF)
 (struct __sk_buff* skb) {
-    return bpf_traffic_account(skb, INGRESS, /* is_4_19 */ true);
+    return bpf_traffic_account(skb, INGRESS, TRACE_OFF, KVER(4, 19, 0));
 }
 
 DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/ingress/stats$4_14", AID_ROOT, AID_SYSTEM,
                                 bpf_cgroup_ingress_4_14, KVER_NONE, KVER(4, 19, 0))
 (struct __sk_buff* skb) {
-    return bpf_traffic_account(skb, INGRESS, /* is_4_19 */ false);
+    return bpf_traffic_account(skb, INGRESS, TRACE_OFF, KVER_NONE);
+}
+
+DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace", AID_ROOT, AID_SYSTEM,
+                    bpf_cgroup_egress_trace, KVER(5, 8, 0), KVER_INF,
+                    BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, false,
+                    "fs_bpf_netd_readonly", "", false, true, false)
+(struct __sk_buff* skb) {
+    return bpf_traffic_account(skb, EGRESS, TRACE_ON, KVER(5, 8, 0));
 }
 
 DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/egress/stats$4_19", AID_ROOT, AID_SYSTEM,
                                 bpf_cgroup_egress_4_19, KVER(4, 19, 0), KVER_INF)
 (struct __sk_buff* skb) {
-    return bpf_traffic_account(skb, EGRESS, /* is_4_19 */ true);
+    return bpf_traffic_account(skb, EGRESS, TRACE_OFF, KVER(4, 19, 0));
 }
 
 DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/egress/stats$4_14", AID_ROOT, AID_SYSTEM,
                                 bpf_cgroup_egress_4_14, KVER_NONE, KVER(4, 19, 0))
 (struct __sk_buff* skb) {
-    return bpf_traffic_account(skb, EGRESS, /* is_4_19 */ false);
+    return bpf_traffic_account(skb, EGRESS, TRACE_OFF, KVER_NONE);
 }
 
 // WARNING: Android T's non-updatable netd depends on the name of this program.
@@ -389,9 +500,8 @@
     // Clat daemon does not generate new traffic, all its traffic is accounted for already
     // on the v4-* interfaces (except for the 20 (or 28) extra bytes of IPv6 vs IPv4 overhead,
     // but that can be corrected for later when merging v4-foo stats into interface foo's).
-    // TODO: remove sock_uid check once Nat464Xlat javaland adds the socket tag AID_CLAT for clat.
+    // CLAT sockets are created by system server and tagged as uid CLAT, see tagSocketAsClat()
     uint32_t sock_uid = bpf_get_socket_uid(skb);
-    if (sock_uid == AID_CLAT) return BPF_NOMATCH;
     if (sock_uid == AID_SYSTEM) {
         uint64_t cookie = bpf_get_socket_cookie(skb);
         UidTagValue* utag = bpf_cookie_tag_map_lookup_elem(&cookie);
@@ -399,7 +509,7 @@
     }
 
     uint32_t key = skb->ifindex;
-    update_iface_stats_map(skb, EGRESS, &key);
+    update_iface_stats_map(skb, &key, EGRESS, KVER_NONE);
     return BPF_MATCH;
 }
 
@@ -412,7 +522,7 @@
     // Keep that in mind when moving this out of iptables xt_bpf and into tc ingress (or xdp).
 
     uint32_t key = skb->ifindex;
-    update_iface_stats_map(skb, INGRESS, &key);
+    update_iface_stats_map(skb, &key, INGRESS, KVER_NONE);
     return BPF_MATCH;
 }
 
@@ -422,7 +532,7 @@
     if (is_received_skb(skb)) {
         // Account for ingress traffic before tc drops it.
         uint32_t key = skb->ifindex;
-        update_iface_stats_map(skb, INGRESS, &key);
+        update_iface_stats_map(skb, &key, INGRESS, KVER_NONE);
     }
     return TC_ACT_UNSPEC;
 }
diff --git a/bpf_progs/bpf_shared.h b/bpf_progs/netd.h
similarity index 92%
rename from bpf_progs/bpf_shared.h
rename to bpf_progs/netd.h
index cc88680..be604f9 100644
--- a/bpf_progs/bpf_shared.h
+++ b/bpf_progs/netd.h
@@ -69,6 +69,24 @@
     uint64_t tcpTxPackets;
 } Stats;
 
+typedef struct {
+  uint64_t timestampNs;
+  uint32_t ifindex;
+  uint32_t length;
+
+  uint32_t uid;
+  uint32_t tag;
+
+  __be16 sport;
+  __be16 dport;
+
+  bool egress;
+  uint8_t ipProto;
+  uint8_t tcpFlags;
+  uint8_t ipVersion; // 4=IPv4, 6=IPv6, 0=unknown
+} PacketTrace;
+STRUCT_SIZE(PacketTrace, 8+4+4 + 4+4 + 2+2 + 1+1+1+1);
+
 // Since we cannot garbage collect the stats map since device boot, we need to make these maps as
 // large as possible. The maximum size of number of map entries we can have is depend on the rlimit
 // of MEM_LOCK granted to netd. The memory space needed by each map can be calculated by the
@@ -87,7 +105,8 @@
 // dozable_uid_map:     key:  4 bytes, value:  1 bytes, cost:  145216 bytes    =   145Kbytes
 // standby_uid_map:     key:  4 bytes, value:  1 bytes, cost:  145216 bytes    =   145Kbytes
 // powersave_uid_map:   key:  4 bytes, value:  1 bytes, cost:  145216 bytes    =   145Kbytes
-// total:                                                                         4930Kbytes
+// packet_trace_ringbuf:key:  0 bytes, value: 24 bytes, cost:   32768 bytes    =    32Kbytes
+// total:                                                                         4962Kbytes
 // It takes maximum 4.9MB kernel memory space if all maps are full, which requires any devices
 // running this module to have a memlock rlimit to be larger then 5MB. In the old qtaguid module,
 // we don't have a total limit for data entries but only have limitation of tags each uid can have.
@@ -102,6 +121,7 @@
 static const int IFACE_STATS_MAP_SIZE = 1000;
 static const int CONFIGURATION_MAP_SIZE = 2;
 static const int UID_OWNER_MAP_SIZE = 4000;
+static const int PACKET_TRACE_BUF_SIZE = 32 * 1024;
 
 #ifdef __cplusplus
 
@@ -145,6 +165,8 @@
 #define CONFIGURATION_MAP_PATH BPF_NETD_PATH "map_netd_configuration_map"
 #define UID_OWNER_MAP_PATH BPF_NETD_PATH "map_netd_uid_owner_map"
 #define UID_PERMISSION_MAP_PATH BPF_NETD_PATH "map_netd_uid_permission_map"
+#define PACKET_TRACE_RINGBUF_PATH BPF_NETD_PATH "map_netd_packet_trace_ringbuf"
+#define PACKET_TRACE_ENABLED_MAP_PATH BPF_NETD_PATH "map_netd_packet_trace_enabled_map"
 
 #endif // __cplusplus
 
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index a8612df..80d1a41 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -38,13 +38,7 @@
 // Warning: values other than AID_ROOT don't work for map uid on BpfLoader < v0.21
 #define TETHERING_UID AID_ROOT
 
-#ifdef INPROCESS
-#define DEFAULT_BPF_MAP_SELINUX_CONTEXT "fs_bpf_net_shared"
-#define DEFAULT_BPF_PROG_SELINUX_CONTEXT "fs_bpf_net_shared"
-#define TETHERING_GID AID_SYSTEM
-#else
 #define TETHERING_GID AID_NETWORK_STACK
-#endif
 
 #include "bpf_helpers.h"
 #include "bpf_net_helpers.h"
@@ -131,7 +125,7 @@
                    TETHERING_GID)
 
 static inline __always_inline int do_forward6(struct __sk_buff* skb, const bool is_ethernet,
-        const bool downstream) {
+        const bool downstream, const unsigned kver) {
     // Must be meta-ethernet IPv6 frame
     if (skb->protocol != htons(ETH_P_IPV6)) return TC_ACT_PIPE;
 
@@ -232,13 +226,13 @@
     // This would require a much newer kernel with newer ebpf accessors.
     // (This is also blindly assuming 12 bytes of tcp timestamp option in tcp header)
     uint64_t packets = 1;
-    uint64_t bytes = skb->len;
-    if (bytes > v->pmtu) {
-        const int tcp_overhead = sizeof(struct ipv6hdr) + sizeof(struct tcphdr) + 12;
-        const int mss = v->pmtu - tcp_overhead;
-        const uint64_t payload = bytes - tcp_overhead;
+    uint64_t L3_bytes = skb->len - l2_header_size;
+    if (L3_bytes > v->pmtu) {
+        const int tcp6_overhead = sizeof(struct ipv6hdr) + sizeof(struct tcphdr) + 12;
+        const int mss = v->pmtu - tcp6_overhead;
+        const uint64_t payload = L3_bytes - tcp6_overhead;
         packets = (payload + mss - 1) / mss;
-        bytes = tcp_overhead * packets + payload;
+        L3_bytes = tcp6_overhead * packets + payload;
     }
 
     // Are we past the limit?  If so, then abort...
@@ -247,7 +241,7 @@
     // a packet we let the core stack deal with things.
     // (The core stack needs to handle limits correctly anyway,
     // since we don't offload all traffic in both directions)
-    if (stat_v->rxBytes + stat_v->txBytes + bytes > *limit_v) TC_PUNT(LIMIT_REACHED);
+    if (stat_v->rxBytes + stat_v->txBytes + L3_bytes > *limit_v) TC_PUNT(LIMIT_REACHED);
 
     if (!is_ethernet) {
         // Try to inject an ethernet header, and simply return if we fail.
@@ -287,7 +281,7 @@
     bpf_csum_update(skb, 0xFFFF - ntohs(old_hl) + ntohs(new_hl));
 
     __sync_fetch_and_add(downstream ? &stat_v->rxPackets : &stat_v->txPackets, packets);
-    __sync_fetch_and_add(downstream ? &stat_v->rxBytes : &stat_v->txBytes, bytes);
+    __sync_fetch_and_add(downstream ? &stat_v->rxBytes : &stat_v->txBytes, L3_bytes);
 
     // Overwrite any mac header with the new one
     // For a rawip tx interface it will simply be a bunch of zeroes and later stripped.
@@ -305,13 +299,13 @@
 DEFINE_BPF_PROG("schedcls/tether_downstream6_ether", TETHERING_UID, TETHERING_GID,
                 sched_cls_tether_downstream6_ether)
 (struct __sk_buff* skb) {
-    return do_forward6(skb, /* is_ethernet */ true, /* downstream */ true);
+    return do_forward6(skb, ETHER, DOWNSTREAM, KVER_NONE);
 }
 
 DEFINE_BPF_PROG("schedcls/tether_upstream6_ether", TETHERING_UID, TETHERING_GID,
                 sched_cls_tether_upstream6_ether)
 (struct __sk_buff* skb) {
-    return do_forward6(skb, /* is_ethernet */ true, /* downstream */ false);
+    return do_forward6(skb, ETHER, UPSTREAM, KVER_NONE);
 }
 
 // Note: section names must be unique to prevent programs from appending to each other,
@@ -331,13 +325,13 @@
 DEFINE_BPF_PROG_KVER("schedcls/tether_downstream6_rawip$4_14", TETHERING_UID, TETHERING_GID,
                      sched_cls_tether_downstream6_rawip_4_14, KVER(4, 14, 0))
 (struct __sk_buff* skb) {
-    return do_forward6(skb, /* is_ethernet */ false, /* downstream */ true);
+    return do_forward6(skb, RAWIP, DOWNSTREAM, KVER(4, 14, 0));
 }
 
 DEFINE_BPF_PROG_KVER("schedcls/tether_upstream6_rawip$4_14", TETHERING_UID, TETHERING_GID,
                      sched_cls_tether_upstream6_rawip_4_14, KVER(4, 14, 0))
 (struct __sk_buff* skb) {
-    return do_forward6(skb, /* is_ethernet */ false, /* downstream */ false);
+    return do_forward6(skb, RAWIP, UPSTREAM, KVER(4, 14, 0));
 }
 
 // and define no-op stubs for pre-4.14 kernels.
@@ -362,7 +356,8 @@
 static inline __always_inline int do_forward4_bottom(struct __sk_buff* skb,
         const int l2_header_size, void* data, const void* data_end,
         struct ethhdr* eth, struct iphdr* ip, const bool is_ethernet,
-        const bool downstream, const bool updatetime, const bool is_tcp) {
+        const bool downstream, const bool updatetime, const bool is_tcp,
+        const unsigned kver) {
     struct tcphdr* tcph = is_tcp ? (void*)(ip + 1) : NULL;
     struct udphdr* udph = is_tcp ? NULL : (void*)(ip + 1);
 
@@ -449,13 +444,13 @@
     // This would require a much newer kernel with newer ebpf accessors.
     // (This is also blindly assuming 12 bytes of tcp timestamp option in tcp header)
     uint64_t packets = 1;
-    uint64_t bytes = skb->len;
-    if (bytes > v->pmtu) {
-        const int tcp_overhead = sizeof(struct iphdr) + sizeof(struct tcphdr) + 12;
-        const int mss = v->pmtu - tcp_overhead;
-        const uint64_t payload = bytes - tcp_overhead;
+    uint64_t L3_bytes = skb->len - l2_header_size;
+    if (L3_bytes > v->pmtu) {
+        const int tcp4_overhead = sizeof(struct iphdr) + sizeof(struct tcphdr) + 12;
+        const int mss = v->pmtu - tcp4_overhead;
+        const uint64_t payload = L3_bytes - tcp4_overhead;
         packets = (payload + mss - 1) / mss;
-        bytes = tcp_overhead * packets + payload;
+        L3_bytes = tcp4_overhead * packets + payload;
     }
 
     // Are we past the limit?  If so, then abort...
@@ -464,7 +459,7 @@
     // a packet we let the core stack deal with things.
     // (The core stack needs to handle limits correctly anyway,
     // since we don't offload all traffic in both directions)
-    if (stat_v->rxBytes + stat_v->txBytes + bytes > *limit_v) TC_PUNT(LIMIT_REACHED);
+    if (stat_v->rxBytes + stat_v->txBytes + L3_bytes > *limit_v) TC_PUNT(LIMIT_REACHED);
 
     if (!is_ethernet) {
         // Try to inject an ethernet header, and simply return if we fail.
@@ -540,7 +535,7 @@
     if (updatetime) v->last_used = bpf_ktime_get_boot_ns();
 
     __sync_fetch_and_add(downstream ? &stat_v->rxPackets : &stat_v->txPackets, packets);
-    __sync_fetch_and_add(downstream ? &stat_v->rxBytes : &stat_v->txBytes, bytes);
+    __sync_fetch_and_add(downstream ? &stat_v->rxBytes : &stat_v->txBytes, L3_bytes);
 
     // Redirect to forwarded interface.
     //
@@ -552,7 +547,7 @@
 }
 
 static inline __always_inline int do_forward4(struct __sk_buff* skb, const bool is_ethernet,
-        const bool downstream, const bool updatetime) {
+        const bool downstream, const bool updatetime, const unsigned kver) {
     // Require ethernet dst mac address to be our unicast address.
     if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE;
 
@@ -640,10 +635,10 @@
     // if the underlying requisite kernel support (bpf_ktime_get_boot_ns) was backported.
     if (is_tcp) {
       return do_forward4_bottom(skb, l2_header_size, data, data_end, eth, ip,
-                                is_ethernet, downstream, updatetime, /* is_tcp */ true);
+                                is_ethernet, downstream, updatetime, /* is_tcp */ true, kver);
     } else {
       return do_forward4_bottom(skb, l2_header_size, data, data_end, eth, ip,
-                                is_ethernet, downstream, updatetime, /* is_tcp */ false);
+                                is_ethernet, downstream, updatetime, /* is_tcp */ false, kver);
     }
 }
 
@@ -652,25 +647,25 @@
 DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_rawip$5_8", TETHERING_UID, TETHERING_GID,
                      sched_cls_tether_downstream4_rawip_5_8, KVER(5, 8, 0))
 (struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ false, /* downstream */ true, /* updatetime */ true);
+    return do_forward4(skb, RAWIP, DOWNSTREAM, UPDATETIME, KVER(5, 8, 0));
 }
 
 DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_rawip$5_8", TETHERING_UID, TETHERING_GID,
                      sched_cls_tether_upstream4_rawip_5_8, KVER(5, 8, 0))
 (struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ false, /* downstream */ false, /* updatetime */ true);
+    return do_forward4(skb, RAWIP, UPSTREAM, UPDATETIME, KVER(5, 8, 0));
 }
 
 DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_ether$5_8", TETHERING_UID, TETHERING_GID,
                      sched_cls_tether_downstream4_ether_5_8, KVER(5, 8, 0))
 (struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ true, /* downstream */ true, /* updatetime */ true);
+    return do_forward4(skb, ETHER, DOWNSTREAM, UPDATETIME, KVER(5, 8, 0));
 }
 
 DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_ether$5_8", TETHERING_UID, TETHERING_GID,
                      sched_cls_tether_upstream4_ether_5_8, KVER(5, 8, 0))
 (struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ true, /* downstream */ false, /* updatetime */ true);
+    return do_forward4(skb, ETHER, UPSTREAM, UPDATETIME, KVER(5, 8, 0));
 }
 
 // Full featured (optional) implementations for 4.14-S, 4.19-S & 5.4-S kernels
@@ -681,7 +676,7 @@
                                     sched_cls_tether_downstream4_rawip_opt,
                                     KVER(4, 14, 0), KVER(5, 8, 0))
 (struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ false, /* downstream */ true, /* updatetime */ true);
+    return do_forward4(skb, RAWIP, DOWNSTREAM, UPDATETIME, KVER(4, 14, 0));
 }
 
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$opt",
@@ -689,7 +684,7 @@
                                     sched_cls_tether_upstream4_rawip_opt,
                                     KVER(4, 14, 0), KVER(5, 8, 0))
 (struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ false, /* downstream */ false, /* updatetime */ true);
+    return do_forward4(skb, RAWIP, UPSTREAM, UPDATETIME, KVER(4, 14, 0));
 }
 
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$opt",
@@ -697,7 +692,7 @@
                                     sched_cls_tether_downstream4_ether_opt,
                                     KVER(4, 14, 0), KVER(5, 8, 0))
 (struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ true, /* downstream */ true, /* updatetime */ true);
+    return do_forward4(skb, ETHER, DOWNSTREAM, UPDATETIME, KVER(4, 14, 0));
 }
 
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$opt",
@@ -705,7 +700,7 @@
                                     sched_cls_tether_upstream4_ether_opt,
                                     KVER(4, 14, 0), KVER(5, 8, 0))
 (struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ true, /* downstream */ false, /* updatetime */ true);
+    return do_forward4(skb, ETHER, UPSTREAM, UPDATETIME, KVER(4, 14, 0));
 }
 
 // Partial (TCP-only: will not update 'last_used' field) implementations for 4.14+ kernels.
@@ -725,13 +720,13 @@
 DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$5_4", TETHERING_UID, TETHERING_GID,
                            sched_cls_tether_downstream4_rawip_5_4, KVER(5, 4, 0), KVER(5, 8, 0))
 (struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ false, /* downstream */ true, /* updatetime */ false);
+    return do_forward4(skb, RAWIP, DOWNSTREAM, NO_UPDATETIME, KVER(5, 4, 0));
 }
 
 DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$5_4", TETHERING_UID, TETHERING_GID,
                            sched_cls_tether_upstream4_rawip_5_4, KVER(5, 4, 0), KVER(5, 8, 0))
 (struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ false, /* downstream */ false, /* updatetime */ false);
+    return do_forward4(skb, RAWIP, UPSTREAM, NO_UPDATETIME, KVER(5, 4, 0));
 }
 
 // RAWIP: Optional for 4.14/4.19 (R) kernels -- which support bpf_skb_change_head().
@@ -742,7 +737,7 @@
                                     sched_cls_tether_downstream4_rawip_4_14,
                                     KVER(4, 14, 0), KVER(5, 4, 0))
 (struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ false, /* downstream */ true, /* updatetime */ false);
+    return do_forward4(skb, RAWIP, DOWNSTREAM, NO_UPDATETIME, KVER(4, 14, 0));
 }
 
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$4_14",
@@ -750,7 +745,7 @@
                                     sched_cls_tether_upstream4_rawip_4_14,
                                     KVER(4, 14, 0), KVER(5, 4, 0))
 (struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ false, /* downstream */ false, /* updatetime */ false);
+    return do_forward4(skb, RAWIP, UPSTREAM, NO_UPDATETIME, KVER(4, 14, 0));
 }
 
 // ETHER: Required for 4.14-Q/R, 4.19-Q/R & 5.4-R kernels.
@@ -758,13 +753,13 @@
 DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$4_14", TETHERING_UID, TETHERING_GID,
                            sched_cls_tether_downstream4_ether_4_14, KVER(4, 14, 0), KVER(5, 8, 0))
 (struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ true, /* downstream */ true, /* updatetime */ false);
+    return do_forward4(skb, ETHER, DOWNSTREAM, NO_UPDATETIME, KVER(4, 14, 0));
 }
 
 DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$4_14", TETHERING_UID, TETHERING_GID,
                            sched_cls_tether_upstream4_ether_4_14, KVER(4, 14, 0), KVER(5, 8, 0))
 (struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ true, /* downstream */ false, /* updatetime */ false);
+    return do_forward4(skb, ETHER, UPSTREAM, NO_UPDATETIME, KVER(4, 14, 0));
 }
 
 // Placeholder (no-op) implementations for older Q kernels
@@ -820,9 +815,9 @@
     if ((void*)(eth + 1) > data_end) return XDP_PASS;
 
     if (eth->h_proto == htons(ETH_P_IPV6))
-        return do_xdp_forward6(ctx, /* is_ethernet */ true, downstream);
+        return do_xdp_forward6(ctx, ETHER, downstream);
     if (eth->h_proto == htons(ETH_P_IP))
-        return do_xdp_forward4(ctx, /* is_ethernet */ true, downstream);
+        return do_xdp_forward4(ctx, ETHER, downstream);
 
     // Anything else we don't know how to handle...
     return XDP_PASS;
@@ -836,8 +831,8 @@
     if (data_end - data < 1) return XDP_PASS;
     const uint8_t v = (*(uint8_t*)data) >> 4;
 
-    if (v == 6) return do_xdp_forward6(ctx, /* is_ethernet */ false, downstream);
-    if (v == 4) return do_xdp_forward4(ctx, /* is_ethernet */ false, downstream);
+    if (v == 6) return do_xdp_forward6(ctx, RAWIP, downstream);
+    if (v == 4) return do_xdp_forward4(ctx, RAWIP, downstream);
 
     // Anything else we don't know how to handle...
     return XDP_PASS;
@@ -848,22 +843,22 @@
 
 DEFINE_XDP_PROG("xdp/tether_downstream_ether",
                  xdp_tether_downstream_ether) {
-    return do_xdp_forward_ether(ctx, /* downstream */ true);
+    return do_xdp_forward_ether(ctx, DOWNSTREAM);
 }
 
 DEFINE_XDP_PROG("xdp/tether_downstream_rawip",
                  xdp_tether_downstream_rawip) {
-    return do_xdp_forward_rawip(ctx, /* downstream */ true);
+    return do_xdp_forward_rawip(ctx, DOWNSTREAM);
 }
 
 DEFINE_XDP_PROG("xdp/tether_upstream_ether",
                  xdp_tether_upstream_ether) {
-    return do_xdp_forward_ether(ctx, /* downstream */ false);
+    return do_xdp_forward_ether(ctx, UPSTREAM);
 }
 
 DEFINE_XDP_PROG("xdp/tether_upstream_rawip",
                  xdp_tether_upstream_rawip) {
-    return do_xdp_forward_rawip(ctx, /* downstream */ false);
+    return do_xdp_forward_rawip(ctx, UPSTREAM);
 }
 
 LICENSE("Apache 2.0");
diff --git a/bpf_progs/offload@inprocess.c b/bpf_progs/offload@inprocess.c
deleted file mode 120000
index 4092e0d..0000000
--- a/bpf_progs/offload@inprocess.c
+++ /dev/null
@@ -1 +0,0 @@
-offload.c
\ No newline at end of file
diff --git a/bpf_progs/test.c b/bpf_progs/test.c
index d1f780f..091743c 100644
--- a/bpf_progs/test.c
+++ b/bpf_progs/test.c
@@ -32,13 +32,7 @@
 // Warning: values other than AID_ROOT don't work for map uid on BpfLoader < v0.21
 #define TETHERING_UID AID_ROOT
 
-#ifdef INPROCESS
-#define DEFAULT_BPF_MAP_SELINUX_CONTEXT "fs_bpf_net_shared"
-#define DEFAULT_BPF_PROG_SELINUX_CONTEXT "fs_bpf_net_shared"
-#define TETHERING_GID AID_SYSTEM
-#else
 #define TETHERING_GID AID_NETWORK_STACK
-#endif
 
 // This is non production code, only used for testing
 // Needed because the bitmap array definition is non-kosher for pre-T OS devices.
diff --git a/bpf_progs/test@inprocess.c b/bpf_progs/test@inprocess.c
deleted file mode 120000
index aeebb26..0000000
--- a/bpf_progs/test@inprocess.c
+++ /dev/null
@@ -1 +0,0 @@
-test.c
\ No newline at end of file
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index d40fad9..ffa2857 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -46,6 +46,7 @@
     libs: [
         "unsupportedappusage",
         "app-compat-annotations",
+        "androidx.annotation_annotation",
     ],
     impl_only_libs: [
         // The build system will use framework-bluetooth module_current stubs, because
@@ -139,7 +140,7 @@
         "//packages/modules/Connectivity/apex",
         "//packages/modules/Connectivity/service", // For R8 only
         "//packages/modules/Connectivity/service-t",
-        "//packages/modules/Connectivity/nearby/service",
+        "//packages/modules/Connectivity/nearby:__subpackages__",
         "//frameworks/base",
 
         // Tests using hidden APIs
@@ -156,7 +157,6 @@
         "//frameworks/opt/telephony/tests/telephonytests",
         "//packages/modules/CaptivePortalLogin/tests",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
-        "//packages/modules/Connectivity/nearby/tests:__subpackages__",
         "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/IPsec/tests/iketests",
         "//packages/modules/NetworkStack/tests:__subpackages__",
@@ -164,6 +164,7 @@
     ],
 }
 
+// This rule is not used anymore(b/268440216).
 platform_compat_config {
     name: "connectivity-t-platform-compat-config",
     src: ":framework-connectivity-t",
diff --git a/framework-t/Sources.bp b/framework-t/Sources.bp
index 391a562..b8eb1f6 100644
--- a/framework-t/Sources.bp
+++ b/framework-t/Sources.bp
@@ -16,15 +16,13 @@
 
 filegroup {
     name: "framework-connectivity-tiramisu-updatable-sources",
+    defaults: ["framework-sources-module-defaults"],
     srcs: [
         "src/**/*.java",
         "src/**/*.aidl",
     ],
     path: "src",
-    visibility: [
-        "//frameworks/base",
-        "//packages/modules/Connectivity:__subpackages__",
-    ],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
 }
 
 cc_library_shared {
diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt
index eb77288..86745d4 100644
--- a/framework-t/api/current.txt
+++ b/framework-t/api/current.txt
@@ -192,15 +192,20 @@
     method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void discoverServices(@NonNull String, int, @NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.DiscoveryListener);
     method public void registerService(android.net.nsd.NsdServiceInfo, int, android.net.nsd.NsdManager.RegistrationListener);
     method public void registerService(@NonNull android.net.nsd.NsdServiceInfo, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.RegistrationListener);
-    method public void resolveService(android.net.nsd.NsdServiceInfo, android.net.nsd.NsdManager.ResolveListener);
-    method public void resolveService(@NonNull android.net.nsd.NsdServiceInfo, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.ResolveListener);
+    method public void registerServiceInfoCallback(@NonNull android.net.nsd.NsdServiceInfo, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.ServiceInfoCallback);
+    method @Deprecated public void resolveService(android.net.nsd.NsdServiceInfo, android.net.nsd.NsdManager.ResolveListener);
+    method @Deprecated public void resolveService(@NonNull android.net.nsd.NsdServiceInfo, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.NsdManager.ResolveListener);
     method public void stopServiceDiscovery(android.net.nsd.NsdManager.DiscoveryListener);
+    method public void stopServiceResolution(@NonNull android.net.nsd.NsdManager.ResolveListener);
     method public void unregisterService(android.net.nsd.NsdManager.RegistrationListener);
+    method public void unregisterServiceInfoCallback(@NonNull android.net.nsd.NsdManager.ServiceInfoCallback);
     field public static final String ACTION_NSD_STATE_CHANGED = "android.net.nsd.STATE_CHANGED";
     field public static final String EXTRA_NSD_STATE = "nsd_state";
     field public static final int FAILURE_ALREADY_ACTIVE = 3; // 0x3
+    field public static final int FAILURE_BAD_PARAMETERS = 6; // 0x6
     field public static final int FAILURE_INTERNAL_ERROR = 0; // 0x0
     field public static final int FAILURE_MAX_LIMIT = 4; // 0x4
+    field public static final int FAILURE_OPERATION_NOT_RUNNING = 5; // 0x5
     field public static final int NSD_STATE_DISABLED = 1; // 0x1
     field public static final int NSD_STATE_ENABLED = 2; // 0x2
     field public static final int PROTOCOL_DNS_SD = 1; // 0x1
@@ -223,22 +228,33 @@
   }
 
   public static interface NsdManager.ResolveListener {
+    method public default void onResolutionStopped(@NonNull android.net.nsd.NsdServiceInfo);
     method public void onResolveFailed(android.net.nsd.NsdServiceInfo, int);
     method public void onServiceResolved(android.net.nsd.NsdServiceInfo);
+    method public default void onStopResolutionFailed(@NonNull android.net.nsd.NsdServiceInfo, int);
+  }
+
+  public static interface NsdManager.ServiceInfoCallback {
+    method public void onServiceInfoCallbackRegistrationFailed(int);
+    method public void onServiceInfoCallbackUnregistered();
+    method public void onServiceLost();
+    method public void onServiceUpdated(@NonNull android.net.nsd.NsdServiceInfo);
   }
 
   public final class NsdServiceInfo implements android.os.Parcelable {
     ctor public NsdServiceInfo();
     method public int describeContents();
     method public java.util.Map<java.lang.String,byte[]> getAttributes();
-    method public java.net.InetAddress getHost();
+    method @Deprecated public java.net.InetAddress getHost();
+    method @NonNull public java.util.List<java.net.InetAddress> getHostAddresses();
     method @Nullable public android.net.Network getNetwork();
     method public int getPort();
     method public String getServiceName();
     method public String getServiceType();
     method public void removeAttribute(String);
     method public void setAttribute(String, String);
-    method public void setHost(java.net.InetAddress);
+    method @Deprecated public void setHost(java.net.InetAddress);
+    method public void setHostAddresses(@NonNull java.util.List<java.net.InetAddress>);
     method public void setNetwork(@Nullable android.net.Network);
     method public void setPort(int);
     method public void setServiceName(String);
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index c2d245c..87b0a64 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -260,6 +260,7 @@
   public class IpSecManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void applyTunnelModeTransform(@NonNull android.net.IpSecManager.IpSecTunnelInterface, int, @NonNull android.net.IpSecTransform) throws java.io.IOException;
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecManager.IpSecTunnelInterface createIpSecTunnelInterface(@NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull android.net.Network) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+    method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void startTunnelModeTransformMigration(@NonNull android.net.IpSecTransform, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress);
   }
 
   public static final class IpSecManager.IpSecTunnelInterface implements java.lang.AutoCloseable {
diff --git a/framework-t/src/android/net/IIpSecService.aidl b/framework-t/src/android/net/IIpSecService.aidl
index 933256a..88ffd0e 100644
--- a/framework-t/src/android/net/IIpSecService.aidl
+++ b/framework-t/src/android/net/IIpSecService.aidl
@@ -66,6 +66,12 @@
     IpSecTransformResponse createTransform(
             in IpSecConfig c, in IBinder binder, in String callingPackage);
 
+    void migrateTransform(
+            int transformId,
+            in String newSourceAddress,
+            in String newDestinationAddress,
+            in String callingPackage);
+
     void deleteTransform(int transformId);
 
     void applyTransportModeTransform(
diff --git a/framework-t/src/android/net/IpSecManager.java b/framework-t/src/android/net/IpSecManager.java
index 9cceac2..3afa6ef 100644
--- a/framework-t/src/android/net/IpSecManager.java
+++ b/framework-t/src/android/net/IpSecManager.java
@@ -37,6 +37,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
 
 import dalvik.system.CloseGuard;
 
@@ -65,6 +66,24 @@
     private static final String TAG = "IpSecManager";
 
     /**
+     * Feature flag to declare the kernel support of updating IPsec SAs.
+     *
+     * <p>Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+     * has the requisite kernel support for migrating IPsec tunnels to new source/destination
+     * addresses.
+     *
+     * <p>This feature implies that the device supports XFRM Migration (CONFIG_XFRM_MIGRATE) and has
+     * the kernel fixes to allow XFRM Migration correctly
+     *
+     * @see android.content.pm.PackageManager#FEATURE_IPSEC_TUNNEL_MIGRATION
+     * @hide
+     */
+    // Redefine this flag here so that IPsec code shipped in a mainline module can build on old
+    // platforms before FEATURE_IPSEC_TUNNEL_MIGRATION API is released.
+    public static final String FEATURE_IPSEC_TUNNEL_MIGRATION =
+            "android.software.ipsec_tunnel_migration";
+
+    /**
      * Used when applying a transform to direct traffic through an {@link IpSecTransform}
      * towards the host.
      *
@@ -254,7 +273,7 @@
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
-            mCloseGuard.open("open");
+            mCloseGuard.open("close");
         }
 
         /** @hide */
@@ -591,7 +610,7 @@
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
-            mCloseGuard.open("constructor");
+            mCloseGuard.open("close");
         }
 
         /** Get the encapsulation socket's file descriptor. */
@@ -804,16 +823,18 @@
          * Update the underlying network for this IpSecTunnelInterface.
          *
          * <p>This new underlying network will be used for all transforms applied AFTER this call is
-         * complete. Before new {@link IpSecTransform}(s) with matching addresses are applied to
-         * this tunnel interface, traffic will still use the old SA, and be routed on the old
+         * complete. Before {@link IpSecTransform}(s) with matching addresses are applied to this
+         * tunnel interface, traffic will still use the old transform, and be routed on the old
          * underlying network.
          *
          * <p>To migrate IPsec tunnel mode traffic, a caller should:
          *
          * <ol>
          *   <li>Update the IpSecTunnelInterface’s underlying network.
-         *   <li>Apply {@link IpSecTransform}(s) with matching addresses to this
-         *       IpSecTunnelInterface.
+         *   <li>Apply the new {@link IpSecTransform}(s) to this IpSecTunnelInterface. These can be
+         *       new {@link IpSecTransform}(s) with matching addresses, or {@link IpSecTransform}(s)
+         *       that have started migration (see {@link
+         *       IpSecManager#startTunnelModeTransformMigration}).
          * </ol>
          *
          * @param underlyingNetwork the new {@link Network} that will carry traffic for this tunnel.
@@ -822,7 +843,6 @@
          *     method will throw an {@link IllegalArgumentException}. If the IpSecTunnelInterface is
          *     later added to this network, all outbound traffic will be blackholed.
          */
-        // TODO: b/169171001 Update the documentation when transform migration is supported.
         // The purpose of making updating network and applying transforms separate is to leave open
         // the possibility to support lossless migration procedures. To do that, Android platform
         // will need to support multiple inbound tunnel mode transforms, just like it can support
@@ -871,7 +891,7 @@
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
-            mCloseGuard.open("constructor");
+            mCloseGuard.open("close");
         }
 
         /**
@@ -988,6 +1008,60 @@
     }
 
     /**
+     * Migrate an active Tunnel Mode IPsec Transform to new source/destination addresses.
+     *
+     * <p>Begins the process of migrating a transform and cache the new addresses. To complete the
+     * migration once started, callers MUST apply the same transform to the appropriate tunnel using
+     * {@link IpSecManager#applyTunnelModeTransform}. Otherwise, the address update will not be
+     * committed and the transform will still only process traffic between the current source and
+     * destination address. One common use case is that the control plane will start the migration
+     * process and then hand off the transform to the IPsec caller to perform the actual migration
+     * when the tunnel is ready.
+     *
+     * <p>If this method is called multiple times before {@link
+     * IpSecManager#applyTunnelModeTransform} is called, when the transform is applied, it will be
+     * migrated to the addresses from the last call.
+     *
+     * <p>The provided source and destination addresses MUST share the same address family, but they
+     * can have a different family from the current addresses.
+     *
+     * <p>Transform migration is only supported for tunnel mode transforms. Calling this method on
+     * other types of transforms will throw an {@code UnsupportedOperationException}.
+     *
+     * @see IpSecTunnelInterface#setUnderlyingNetwork
+     * @param transform a tunnel mode {@link IpSecTransform}
+     * @param newSourceAddress the new source address
+     * @param newDestinationAddress the new destination address
+     * @hide
+     */
+    @SystemApi
+    @RequiresFeature(FEATURE_IPSEC_TUNNEL_MIGRATION)
+    @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
+    public void startTunnelModeTransformMigration(
+            @NonNull IpSecTransform transform,
+            @NonNull InetAddress newSourceAddress,
+            @NonNull InetAddress newDestinationAddress) {
+        if (!SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException(
+                    "Transform migration only supported for Android 14+");
+        }
+
+        Objects.requireNonNull(transform, "transform was null");
+        Objects.requireNonNull(newSourceAddress, "newSourceAddress was null");
+        Objects.requireNonNull(newDestinationAddress, "newDestinationAddress was null");
+
+        try {
+            mService.migrateTransform(
+                    transform.getResourceId(),
+                    newSourceAddress.getHostAddress(),
+                    newDestinationAddress.getHostAddress(),
+                    mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * @hide
      */
     public IpSecTransformResponse createTransform(IpSecConfig config, IBinder binder,
diff --git a/framework-t/src/android/net/IpSecTransform.java b/framework-t/src/android/net/IpSecTransform.java
index 68ae5de..c236b6c 100644
--- a/framework-t/src/android/net/IpSecTransform.java
+++ b/framework-t/src/android/net/IpSecTransform.java
@@ -126,7 +126,7 @@
                 checkResultStatus(status);
                 mResourceId = result.resourceId;
                 Log.d(TAG, "Added Transform with Id " + mResourceId);
-                mCloseGuard.open("build");
+                mCloseGuard.open("close");
             } catch (ServiceSpecificException e) {
                 throw IpSecManager.rethrowUncheckedExceptionFromServiceSpecificException(e);
             }
diff --git a/framework-t/src/android/net/NetworkIdentity.java b/framework-t/src/android/net/NetworkIdentity.java
index edfd21c..947a092 100644
--- a/framework-t/src/android/net/NetworkIdentity.java
+++ b/framework-t/src/android/net/NetworkIdentity.java
@@ -32,6 +32,7 @@
 import android.net.wifi.WifiInfo;
 import android.service.NetworkIdentityProto;
 import android.telephony.TelephonyManager;
+import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.net.module.util.BitUtils;
@@ -406,10 +407,18 @@
             setOemManaged(getOemBitfield(snapshot.getNetworkCapabilities()));
 
             if (mType == TYPE_WIFI) {
-                final TransportInfo transportInfo = snapshot.getNetworkCapabilities()
-                        .getTransportInfo();
+                final NetworkCapabilities nc = snapshot.getNetworkCapabilities();
+                final TransportInfo transportInfo = nc.getTransportInfo();
                 if (transportInfo instanceof WifiInfo) {
                     final WifiInfo info = (WifiInfo) transportInfo;
+                    // Log.wtf to catch trying to set a null wifiNetworkKey into NetworkIdentity.
+                    // See b/266598304. The problematic data that has null wifi network key is
+                    // thrown out when storing data, which is handled by the service.
+                    if (info.getNetworkKey() == null) {
+                        Log.wtf(TAG, "WifiInfo contains a null wifiNetworkKey and it will"
+                                + " be set into NetworkIdentity, netId=" + snapshot.getNetwork()
+                                + "NetworkCapabilities=" + nc);
+                    }
                     setWifiNetworkKey(info.getNetworkKey());
                 }
             } else if (mType == TYPE_TEST) {
diff --git a/framework-t/src/android/net/NetworkStatsAccess.java b/framework-t/src/android/net/NetworkStatsAccess.java
index b64fbdb..23902dc 100644
--- a/framework-t/src/android/net/NetworkStatsAccess.java
+++ b/framework-t/src/android/net/NetworkStatsAccess.java
@@ -17,7 +17,6 @@
 package android.net;
 
 import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.TrafficStats.UID_REMOVED;
 import static android.net.TrafficStats.UID_TETHERING;
@@ -33,6 +32,8 @@
 import android.os.UserHandle;
 import android.telephony.TelephonyManager;
 
+import com.android.net.module.util.PermissionUtils;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -100,6 +101,7 @@
          * <li>Device owners.
          * <li>Carrier-privileged applications.
          * <li>The system UID.
+         * <li>NetworkStack application.
          * </ul>
          */
         int DEVICE = 3;
@@ -111,22 +113,23 @@
         final DevicePolicyManager mDpm = context.getSystemService(DevicePolicyManager.class);
         final TelephonyManager tm = (TelephonyManager)
                 context.getSystemService(Context.TELEPHONY_SERVICE);
-        boolean hasCarrierPrivileges;
-        final long token = Binder.clearCallingIdentity();
+        final boolean hasCarrierPrivileges;
+        final boolean isDeviceOwner;
+        long token = Binder.clearCallingIdentity();
         try {
             hasCarrierPrivileges = tm != null
                     && tm.checkCarrierPrivilegesForPackageAnyPhone(callingPackage)
                             == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+            isDeviceOwner = mDpm != null && mDpm.isDeviceOwnerApp(callingPackage);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
 
-        final boolean isDeviceOwner = mDpm != null && mDpm.isDeviceOwnerApp(callingPackage);
         final int appId = UserHandle.getAppId(callingUid);
 
-        final boolean isNetworkStack = context.checkPermission(
-                android.Manifest.permission.NETWORK_STACK, callingPid, callingUid)
-                == PERMISSION_GRANTED;
+        final boolean isNetworkStack = PermissionUtils.checkAnyPermissionOf(
+                context, callingPid, callingUid, android.Manifest.permission.NETWORK_STACK,
+                NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
 
         if (hasCarrierPrivileges || isDeviceOwner
                 || appId == Process.SYSTEM_UID || isNetworkStack) {
@@ -135,15 +138,20 @@
             return NetworkStatsAccess.Level.DEVICE;
         }
 
-        boolean hasAppOpsPermission = hasAppOpsPermission(context, callingUid, callingPackage);
+        final boolean hasAppOpsPermission =
+                hasAppOpsPermission(context, callingUid, callingPackage);
         if (hasAppOpsPermission || context.checkCallingOrSelfPermission(
                 READ_NETWORK_USAGE_HISTORY) == PackageManager.PERMISSION_GRANTED) {
             return NetworkStatsAccess.Level.DEVICESUMMARY;
         }
 
-        //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode.
-        boolean isProfileOwner = mDpm != null && (mDpm.isProfileOwnerApp(callingPackage)
-                || mDpm.isDeviceOwnerApp(callingPackage));
+        final boolean isProfileOwner;
+        token = Binder.clearCallingIdentity();
+        try {
+            isProfileOwner = mDpm != null && mDpm.isProfileOwnerApp(callingPackage);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
         if (isProfileOwner) {
             // Apps with the AppOps permission, profile owners, and apps with the privileged
             // permission can access data usage for all apps in this user/profile.
diff --git a/framework-t/src/android/net/NetworkTemplate.java b/framework-t/src/android/net/NetworkTemplate.java
index c0ae822..33bd884 100644
--- a/framework-t/src/android/net/NetworkTemplate.java
+++ b/framework-t/src/android/net/NetworkTemplate.java
@@ -47,17 +47,22 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 import android.util.ArraySet;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.NetworkIdentityUtils;
-import com.android.net.module.util.NetworkStatsUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 import java.util.SortedSet;
@@ -71,6 +76,8 @@
  */
 @SystemApi(client = MODULE_LIBRARIES)
 public final class NetworkTemplate implements Parcelable {
+    private static final String TAG = NetworkTemplate.class.getSimpleName();
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "MATCH_" }, value = {
@@ -89,18 +96,6 @@
     public static final int MATCH_WIFI = 4;
     /** Match rule to match ethernet networks. */
     public static final int MATCH_ETHERNET = 5;
-    /**
-     * Match rule to match all cellular networks.
-     *
-     * @hide
-     */
-    public static final int MATCH_MOBILE_WILDCARD = 6;
-    /**
-     * Match rule to match all wifi networks.
-     *
-     * @hide
-     */
-    public static final int MATCH_WIFI_WILDCARD = 7;
     /** Match rule to match bluetooth networks. */
     public static final int MATCH_BLUETOOTH = 8;
     /**
@@ -178,8 +173,6 @@
             case MATCH_MOBILE:
             case MATCH_WIFI:
             case MATCH_ETHERNET:
-            case MATCH_MOBILE_WILDCARD:
-            case MATCH_WIFI_WILDCARD:
             case MATCH_BLUETOOTH:
             case MATCH_PROXY:
             case MATCH_CARRIER:
@@ -191,6 +184,23 @@
         }
     }
 
+    private static Set<String> setOf(@Nullable final String item) {
+        if (item == null) {
+            // Set.of will throw if item is null
+            final Set<String> set = new HashSet<>();
+            set.add(null);
+            return Collections.unmodifiableSet(set);
+        } else {
+            return Set.of(item);
+        }
+    }
+
+    private static void throwAtLeastU() {
+        if (SdkLevel.isAtLeastU()) {
+            throw new UnsupportedOperationException("Method not supported on Android U or above");
+        }
+    }
+
     /**
      * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with
      * the given IMSI.
@@ -203,7 +213,7 @@
             publicAlternatives = "Use {@code Builder} instead.")
     public static NetworkTemplate buildTemplateMobileAll(@NonNull String subscriberId) {
         return new NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES)
-                .setSubscriberIds(Set.of(subscriberId)).build();
+                .setSubscriberIds(setOf(subscriberId)).build();
     }
 
     /**
@@ -258,8 +268,122 @@
         return new NetworkTemplate.Builder(MATCH_ETHERNET).build();
     }
 
+    /**
+     * Template to combine all {@link ConnectivityManager#TYPE_BLUETOOTH} style
+     * networks together.
+     *
+     * @hide
+     */
+    // TODO(b/270089918): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
+    public static NetworkTemplate buildTemplateBluetooth() {
+        // TODO : this is part of hidden-o txt, does that mean it should be annotated with
+        // @UnsupportedAppUsage(maxTargetSdk = O) ? If yes, can't throwAtLeastU() lest apps
+        // targeting O- crash on those devices.
+        return new NetworkTemplate.Builder(MATCH_BLUETOOTH).build();
+    }
+
+    /**
+     * Template to combine all {@link ConnectivityManager#TYPE_PROXY} style
+     * networks together.
+     *
+     * @hide
+     */
+    // TODO(b/270089918): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
+    public static NetworkTemplate buildTemplateProxy() {
+        // TODO : this is part of hidden-o txt, does that mean it should be annotated with
+        // @UnsupportedAppUsage(maxTargetSdk = O) ? If yes, can't throwAtLeastU() lest apps
+        // targeting O- crash on those devices.
+        return new NetworkTemplate(MATCH_PROXY, null, null);
+    }
+
+    /**
+     * Template to match all metered carrier networks with the given IMSI.
+     *
+     * @hide
+     */
+    // TODO(b/273963543): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
+    public static NetworkTemplate buildTemplateCarrierMetered(@NonNull String subscriberId) {
+        throwAtLeastU();
+        return new NetworkTemplate.Builder(MATCH_CARRIER)
+                // Set.of will throw if subscriberId is null, which is the historical
+                // behavior and should be preserved.
+                .setSubscriberIds(Set.of(subscriberId))
+                .setMeteredness(METERED_YES)
+                .build();
+    }
+
+    /**
+     * Template to match cellular networks with the given IMSI, {@code ratType} and
+     * {@code metered}. Use {@link #NETWORK_TYPE_ALL} to include all network types when
+     * filtering. See {@code TelephonyManager.NETWORK_TYPE_*}.
+     *
+     * @hide
+     */
+    // TODO(b/273963543): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
+    public static NetworkTemplate buildTemplateMobileWithRatType(@Nullable String subscriberId,
+            int ratType, int metered) {
+        throwAtLeastU();
+        return new NetworkTemplate.Builder(MATCH_MOBILE)
+                .setSubscriberIds(TextUtils.isEmpty(subscriberId)
+                        ? Collections.emptySet()
+                        : Set.of(subscriberId))
+                .setMeteredness(metered)
+                .setRatType(ratType)
+                .build();
+    }
+
+    /**
+     * Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the
+     * given key of the wifi network.
+     *
+     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()}
+     *                  to know details about the key.
+     * @hide
+     */
+    // TODO(b/273963543): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
+    public static NetworkTemplate buildTemplateWifi(@NonNull String wifiNetworkKey) {
+        // TODO : this is part of hidden-o txt, does that mean it should be annotated with
+        // @UnsupportedAppUsage(maxTargetSdk = O) ? If yes, can't throwAtLeastU() lest apps
+        // targeting O- crash on those devices.
+        return new NetworkTemplate.Builder(MATCH_WIFI)
+                // Set.of will throw if wifiNetworkKey is null, which is the historical
+                // behavior and should be preserved.
+                .setWifiNetworkKeys(Set.of(wifiNetworkKey))
+                .build();
+    }
+
+    /**
+     * Template to match all {@link ConnectivityManager#TYPE_WIFI} networks with the given
+     * key of the wifi network and IMSI.
+     *
+     * Call with {@link #WIFI_NETWORK_KEY_ALL} for {@code wifiNetworkKey} to get result regardless
+     * of key of the wifi network.
+     *
+     * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()}
+     *                  to know details about the key.
+     * @param subscriberId the IMSI associated to this wifi network.
+     *
+     * @hide
+     */
+    // TODO(b/273963543): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
+    public static NetworkTemplate buildTemplateWifi(@Nullable String wifiNetworkKey,
+            @Nullable String subscriberId) {
+        throwAtLeastU();
+        return new NetworkTemplate.Builder(MATCH_WIFI)
+                .setSubscriberIds(setOf(subscriberId))
+                .setWifiNetworkKeys(wifiNetworkKey == null
+                        ? Collections.emptySet()
+                        : Set.of(wifiNetworkKey))
+                .build();
+    }
+
     private final int mMatchRule;
-    private final String mSubscriberId;
 
     /**
      * Ugh, templates are designed to target a single subscriber, but we might
@@ -280,23 +404,33 @@
     private final int mRoaming;
     private final int mDefaultNetwork;
     private final int mRatType;
-    /**
-     * The subscriber Id match rule defines how the template should match networks with
-     * specific subscriberId(s). See NetworkTemplate#SUBSCRIBER_ID_MATCH_RULE_* for more detail.
-     */
-    private final int mSubscriberIdMatchRule;
 
     // Bitfield containing OEM network properties{@code NetworkIdentity#OEM_*}.
     private final int mOemManaged;
 
-    private static void checkValidSubscriberIdMatchRule(int matchRule, int subscriberIdMatchRule) {
+    private static void checkValidMatchSubscriberIds(int matchRule, String[] matchSubscriberIds) {
         switch (matchRule) {
-            case MATCH_MOBILE:
+            // CARRIER templates must always specify a valid subscriber ID.
+            // MOBILE templates can have empty matchSubscriberIds but it must not contain a null
+            // subscriber ID.
             case MATCH_CARRIER:
-                // MOBILE and CARRIER templates must always specify a subscriber ID.
-                if (subscriberIdMatchRule == NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL) {
-                    throw new IllegalArgumentException("Invalid SubscriberIdMatchRule "
-                            + "on match rule: " + getMatchRuleName(matchRule));
+                if (matchSubscriberIds.length == 0) {
+                    throw new IllegalArgumentException("matchSubscriberIds may not contain"
+                            + " null for rule " + getMatchRuleName(matchRule));
+                }
+                if (CollectionUtils.contains(matchSubscriberIds, null)) {
+                    throw new IllegalArgumentException("matchSubscriberIds may not contain"
+                            + " null for rule " + getMatchRuleName(matchRule));
+                }
+                break;
+            case MATCH_MOBILE:
+                // Prevent from crash for b/273963543, where the OEMs still call into unsupported
+                // buildTemplateMobileAll with null subscriberId and get crashed.
+                final int firstSdk = Build.VERSION.DEVICE_INITIAL_SDK_INT;
+                if (firstSdk > Build.VERSION_CODES.TIRAMISU
+                        && CollectionUtils.contains(matchSubscriberIds, null)) {
+                    throw new IllegalArgumentException("checkValidMatchSubscriberIds list of ids"
+                            + " may not contain null for rule " + getMatchRuleName(matchRule));
                 }
                 return;
             default:
@@ -312,32 +446,81 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
             publicAlternatives = "Use {@code Builder} instead.")
     public NetworkTemplate(int matchRule, String subscriberId, String wifiNetworkKey) {
-        this(matchRule, subscriberId, new String[] { subscriberId }, wifiNetworkKey);
+        // Older versions used to only match MATCH_MOBILE and MATCH_MOBILE_WILDCARD templates
+        // to metered networks. It is now possible to match mobile with any meteredness, but
+        // in order to preserve backward compatibility of @UnsupportedAppUsage methods, this
+        // constructor passes METERED_YES for these types.
+        // For backwards compatibility, still accept old wildcard match rules (6 and 7 for
+        // MATCH_{MOBILE,WIFI}_WILDCARD) but convert into functionally equivalent non-wildcard
+        // ones.
+        this(getBackwardsCompatibleMatchRule(matchRule),
+                subscriberId != null ? new String[] { subscriberId } : new String[0],
+                wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0],
+                getMeterednessForBackwardsCompatibility(matchRule), ROAMING_ALL,
+                DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL);
+        if (matchRule == 6 || matchRule == 7) {
+            Log.e(TAG, "Use MATCH_MOBILE with empty subscriberIds or MATCH_WIFI with empty "
+                    + "wifiNetworkKeys instead of template with matchRule=" + matchRule);
+        }
+    }
+
+    private static int getBackwardsCompatibleMatchRule(int matchRule) {
+        // Backwards compatibility old constants
+        // Old MATCH_MOBILE_WILDCARD
+        if (6 == matchRule) return MATCH_MOBILE;
+        // Old MATCH_WIFI_WILDCARD
+        if (7 == matchRule) return MATCH_WIFI;
+        return matchRule;
+    }
+
+    private static int getMeterednessForBackwardsCompatibility(int matchRule) {
+        if (getBackwardsCompatibleMatchRule(matchRule) == MATCH_MOBILE
+                || matchRule == MATCH_CARRIER) {
+            return METERED_YES;
+        }
+        return METERED_ALL;
     }
 
     /** @hide */
+    // TODO(b/270089918): Remove this method after no callers.
     public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
             String wifiNetworkKey) {
         // Older versions used to only match MATCH_MOBILE and MATCH_MOBILE_WILDCARD templates
         // to metered networks. It is now possible to match mobile with any meteredness, but
         // in order to preserve backward compatibility of @UnsupportedAppUsage methods, this
-        //constructor passes METERED_YES for these types.
-        this(matchRule, subscriberId, matchSubscriberIds,
+        // constructor passes METERED_YES for these types.
+        this(getBackwardsCompatibleMatchRule(matchRule), matchSubscriberIds,
                 wifiNetworkKey != null ? new String[] { wifiNetworkKey } : new String[0],
-                (matchRule == MATCH_MOBILE || matchRule == MATCH_MOBILE_WILDCARD
-                        || matchRule == MATCH_CARRIER) ? METERED_YES : METERED_ALL,
+                getMeterednessForBackwardsCompatibility(matchRule),
                 ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
-                OEM_MANAGED_ALL, NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT);
+                OEM_MANAGED_ALL);
+        // TODO : this is part of hidden-o txt, does that mean it should be annotated with
+        // @UnsupportedAppUsage(maxTargetSdk = O) ? If yes, can't throwAtLeastU() lest apps
+        // targeting O- crash on those devices.
     }
 
     /** @hide */
+    // TODO(b/269974916): Remove this method after Android U is released.
+    //  This is only used by CTS of Android T.
     public NetworkTemplate(int matchRule, String subscriberId, String[] matchSubscriberIds,
             String[] matchWifiNetworkKeys, int metered, int roaming,
             int defaultNetwork, int ratType, int oemManaged, int subscriberIdMatchRule) {
+        // subscriberId and subscriberIdMatchRule aren't used since they are replaced by
+        // matchSubscriberIds, which could be null to indicate the intention of matching any
+        // subscriberIds.
+        this(getBackwardsCompatibleMatchRule(matchRule),
+                matchSubscriberIds == null ? new String[]{} : matchSubscriberIds,
+                matchWifiNetworkKeys, metered, roaming, defaultNetwork, ratType, oemManaged);
+        throwAtLeastU();
+    }
+
+    /** @hide */
+    public NetworkTemplate(int matchRule, String[] matchSubscriberIds,
+            String[] matchWifiNetworkKeys, int metered, int roaming, int defaultNetwork,
+            int ratType, int oemManaged) {
         Objects.requireNonNull(matchWifiNetworkKeys);
         Objects.requireNonNull(matchSubscriberIds);
         mMatchRule = matchRule;
-        mSubscriberId = subscriberId;
         mMatchSubscriberIds = matchSubscriberIds;
         mMatchWifiNetworkKeys = matchWifiNetworkKeys;
         mMetered = metered;
@@ -345,8 +528,7 @@
         mDefaultNetwork = defaultNetwork;
         mRatType = ratType;
         mOemManaged = oemManaged;
-        mSubscriberIdMatchRule = subscriberIdMatchRule;
-        checkValidSubscriberIdMatchRule(matchRule, subscriberIdMatchRule);
+        checkValidMatchSubscriberIds(matchRule, matchSubscriberIds);
         if (!isKnownMatchRule(matchRule)) {
             throw new IllegalArgumentException("Unknown network template rule " + matchRule
                     + " will not match any identity.");
@@ -355,7 +537,6 @@
 
     private NetworkTemplate(Parcel in) {
         mMatchRule = in.readInt();
-        mSubscriberId = in.readString();
         mMatchSubscriberIds = in.createStringArray();
         mMatchWifiNetworkKeys = in.createStringArray();
         mMetered = in.readInt();
@@ -363,13 +544,11 @@
         mDefaultNetwork = in.readInt();
         mRatType = in.readInt();
         mOemManaged = in.readInt();
-        mSubscriberIdMatchRule = in.readInt();
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mMatchRule);
-        dest.writeString(mSubscriberId);
         dest.writeStringArray(mMatchSubscriberIds);
         dest.writeStringArray(mMatchWifiNetworkKeys);
         dest.writeInt(mMetered);
@@ -377,7 +556,6 @@
         dest.writeInt(mDefaultNetwork);
         dest.writeInt(mRatType);
         dest.writeInt(mOemManaged);
-        dest.writeInt(mSubscriberIdMatchRule);
     }
 
     @Override
@@ -389,10 +567,6 @@
     public String toString() {
         final StringBuilder builder = new StringBuilder("NetworkTemplate: ");
         builder.append("matchRule=").append(getMatchRuleName(mMatchRule));
-        if (mSubscriberId != null) {
-            builder.append(", subscriberId=").append(
-                    NetworkIdentityUtils.scrubSubscriberId(mSubscriberId));
-        }
         if (mMatchSubscriberIds != null) {
             builder.append(", matchSubscriberIds=").append(
                     Arrays.toString(NetworkIdentityUtils.scrubSubscriberIds(mMatchSubscriberIds)));
@@ -414,15 +588,14 @@
         if (mOemManaged != OEM_MANAGED_ALL) {
             builder.append(", oemManaged=").append(getOemManagedNames(mOemManaged));
         }
-        builder.append(", subscriberIdMatchRule=")
-                .append(subscriberIdMatchRuleToString(mSubscriberIdMatchRule));
         return builder.toString();
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mMatchRule, mSubscriberId, Arrays.hashCode(mMatchWifiNetworkKeys),
-                mMetered, mRoaming, mDefaultNetwork, mRatType, mOemManaged, mSubscriberIdMatchRule);
+        return Objects.hash(mMatchRule, Arrays.hashCode(mMatchSubscriberIds),
+                Arrays.hashCode(mMatchWifiNetworkKeys), mMetered, mRoaming, mDefaultNetwork,
+                mRatType, mOemManaged);
     }
 
     @Override
@@ -430,34 +603,28 @@
         if (obj instanceof NetworkTemplate) {
             final NetworkTemplate other = (NetworkTemplate) obj;
             return mMatchRule == other.mMatchRule
-                    && Objects.equals(mSubscriberId, other.mSubscriberId)
                     && mMetered == other.mMetered
                     && mRoaming == other.mRoaming
                     && mDefaultNetwork == other.mDefaultNetwork
                     && mRatType == other.mRatType
                     && mOemManaged == other.mOemManaged
-                    && mSubscriberIdMatchRule == other.mSubscriberIdMatchRule
+                    && Arrays.equals(mMatchSubscriberIds, other.mMatchSubscriberIds)
                     && Arrays.equals(mMatchWifiNetworkKeys, other.mMatchWifiNetworkKeys);
         }
         return false;
     }
 
-    private static String subscriberIdMatchRuleToString(int rule) {
-        switch (rule) {
-            case NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT:
-                return "EXACT_MATCH";
-            case NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL:
-                return "ALL";
-            default:
-                return "Unknown rule " + rule;
-        }
-    }
-
+    // TODO(b/270089918): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
     /** @hide */
     public boolean isMatchRuleMobile() {
+        // TODO : this is part of hidden-o txt, does that mean it should be annotated with
+        // @UnsupportedAppUsage(maxTargetSdk = O) ? If yes, can't throwAtLeastU() lest apps
+        // targeting O- crash on those devices.
         switch (mMatchRule) {
             case MATCH_MOBILE:
-            case MATCH_MOBILE_WILDCARD:
+            // Old MATCH_MOBILE_WILDCARD
+            case 6:
                 return true;
             default:
                 return false;
@@ -467,28 +634,22 @@
     /**
      * Get match rule of the template. See {@code MATCH_*}.
      */
-    @UnsupportedAppUsage
     public int getMatchRule() {
-        // Wildcard rules are not exposed. For external callers, convert wildcard rules to
-        // exposed rules before returning.
-        switch (mMatchRule) {
-            case MATCH_MOBILE_WILDCARD:
-                return MATCH_MOBILE;
-            case MATCH_WIFI_WILDCARD:
-                return MATCH_WIFI;
-            default:
-                return mMatchRule;
-        }
+        return mMatchRule;
     }
 
     /**
      * Get subscriber Id of the template.
+     *
+     * @deprecated User should use {@link #getSubscriberIds} instead.
      * @hide
      */
+    @Deprecated
     @Nullable
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+            publicAlternatives = "Caller should use {@code getSubscriberIds} instead.")
     public String getSubscriberId() {
-        return mSubscriberId;
+        return CollectionUtils.isEmpty(mMatchSubscriberIds) ? null : mMatchSubscriberIds[0];
     }
 
     /**
@@ -575,10 +736,6 @@
                 return matchesWifi(ident);
             case MATCH_ETHERNET:
                 return matchesEthernet(ident);
-            case MATCH_MOBILE_WILDCARD:
-                return matchesMobileWildcard(ident);
-            case MATCH_WIFI_WILDCARD:
-                return matchesWifiWildcard(ident);
             case MATCH_BLUETOOTH:
                 return matchesBluetooth(ident);
             case MATCH_PROXY:
@@ -627,13 +784,13 @@
 
     /**
      * Check if this template matches {@code subscriberId}. Returns true if this
-     * template was created with {@code SUBSCRIBER_ID_MATCH_RULE_ALL}, or with a
-     * {@code mMatchSubscriberIds} array that contains {@code subscriberId}.
+     * template was created with a {@code mMatchSubscriberIds} array that contains
+     * {@code subscriberId} or if {@code mMatchSubscriberIds} is empty.
      *
      * @hide
      */
     public boolean matchesSubscriberId(@Nullable String subscriberId) {
-        return mSubscriberIdMatchRule == NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL
+        return mMatchSubscriberIds.length == 0
                 || CollectionUtils.contains(mMatchSubscriberIds, subscriberId);
     }
 
@@ -646,7 +803,15 @@
      *                  to know details about the key.
      */
     private boolean matchesWifiNetworkKey(@NonNull String wifiNetworkKey) {
-        Objects.requireNonNull(wifiNetworkKey);
+        // Note that this code accepts null wifi network keys because of a past bug where wifi
+        // code was sending a null network key for some connected networks, which isn't expected
+        // and ended up stored in the data on many devices.
+        // A null network key in the data matches a wildcard template (one where
+        // {@code mMatchWifiNetworkKeys} is empty), but not one where {@code MatchWifiNetworkKeys}
+        // contains null. See b/266598304.
+        if (wifiNetworkKey == null) {
+            return CollectionUtils.isEmpty(mMatchWifiNetworkKeys);
+        }
         return CollectionUtils.isEmpty(mMatchWifiNetworkKeys)
                 || CollectionUtils.contains(mMatchWifiNetworkKeys, wifiNetworkKey);
     }
@@ -659,9 +824,9 @@
             // TODO: consider matching against WiMAX subscriber identity
             return true;
         } else {
-            return ident.mType == TYPE_MOBILE && !CollectionUtils.isEmpty(mMatchSubscriberIds)
-                    && CollectionUtils.contains(mMatchSubscriberIds, ident.mSubscriberId)
-                    && matchesCollapsedRatType(ident);
+            return (CollectionUtils.isEmpty(mMatchSubscriberIds)
+                || CollectionUtils.contains(mMatchSubscriberIds, ident.mSubscriberId))
+                && (ident.mType == TYPE_MOBILE && matchesCollapsedRatType(ident));
         }
     }
 
@@ -673,6 +838,8 @@
             case TYPE_WIFI:
                 return matchesSubscriberId(ident.mSubscriberId)
                         && matchesWifiNetworkKey(ident.mWifiNetworkKey);
+            case TYPE_WIFI_P2P:
+                return CollectionUtils.isEmpty(mMatchWifiNetworkKeys);
             default:
                 return false;
         }
@@ -708,24 +875,6 @@
                 || CollectionUtils.contains(mMatchWifiNetworkKeys, ident.mWifiNetworkKey)));
     }
 
-    private boolean matchesMobileWildcard(NetworkIdentity ident) {
-        if (ident.mType == TYPE_WIMAX) {
-            return true;
-        } else {
-            return ident.mType == TYPE_MOBILE && matchesCollapsedRatType(ident);
-        }
-    }
-
-    private boolean matchesWifiWildcard(NetworkIdentity ident) {
-        switch (ident.mType) {
-            case TYPE_WIFI:
-            case TYPE_WIFI_P2P:
-                return true;
-            default:
-                return false;
-        }
-    }
-
     /**
      * Check if matches Bluetooth network template.
      */
@@ -751,10 +900,6 @@
                 return "WIFI";
             case MATCH_ETHERNET:
                 return "ETHERNET";
-            case MATCH_MOBILE_WILDCARD:
-                return "MOBILE_WILDCARD";
-            case MATCH_WIFI_WILDCARD:
-                return "WIFI_WILDCARD";
             case MATCH_BLUETOOTH:
                 return "BLUETOOTH";
             case MATCH_PROXY:
@@ -788,8 +933,7 @@
      * subscribers.
      * <p>
      * For example, given an incoming template matching B, and the currently
-     * active merge set [A,B], we'd return a new template that primarily matches
-     * A, but also matches B.
+     * active merge set [A,B], we'd return a new template that matches both A and B.
      *
      * @hide
      */
@@ -798,21 +942,68 @@
                     + "Callers should have their own logic to merge template for"
                     + " different IMSIs and stop calling this function.")
     public static NetworkTemplate normalize(NetworkTemplate template, String[] merged) {
+        return normalizeImpl(template, Collections.singletonList(merged));
+    }
+
+    /**
+     * Examine the given template and normalize it.
+     * We pick the "lowest" merged subscriber as the primary
+     * for key purposes, and expand the template to match all other merged
+     * subscribers.
+     *
+     * There can be multiple merged subscriberIds for multi-SIM devices.
+     *
+     * <p>
+     * For example, given an incoming template matching B, and the currently
+     * active merge set [A,B], we'd return a new template that matches both A and B.
+     *
+     * @hide
+     */
+    // TODO(b/273963543): Remove this method. This can only be done after there are no more callers,
+    //  including in OEM code which can access this by linking against the framework.
+    public static NetworkTemplate normalize(NetworkTemplate template, List<String[]> mergedList) {
+        throwAtLeastU();
+        return normalizeImpl(template, mergedList);
+    }
+
+    /**
+     * Examine the given template and normalize it.
+     * We pick the "lowest" merged subscriber as the primary
+     * for key purposes, and expand the template to match all other merged
+     * subscribers.
+     *
+     * There can be multiple merged subscriberIds for multi-SIM devices.
+     *
+     * <p>
+     * For example, given an incoming template matching B, and the currently
+     * active merge set [A,B], we'd return a new template that matches both A and B.
+     *
+     * @hide
+     */
+    private static NetworkTemplate normalizeImpl(NetworkTemplate template,
+            List<String[]> mergedList) {
         // Now there are several types of network which uses SubscriberId to store network
         // information. For instances:
         // The TYPE_WIFI with subscriberId means that it is a merged carrier wifi network.
         // The TYPE_CARRIER means that the network associate to specific carrier network.
-        if (template.mSubscriberId == null) return template;
 
-        if (CollectionUtils.contains(merged, template.mSubscriberId)) {
-            // Requested template subscriber is part of the merge group; return
-            // a template that matches all merged subscribers.
-            final String[] matchWifiNetworkKeys = template.mMatchWifiNetworkKeys;
-            // TODO: Use NetworkTemplate.Builder to build a template after NetworkTemplate
-            // could handle incompatible subscriberIds. See b/217805241.
-            return new NetworkTemplate(template.mMatchRule, merged[0], merged,
-                    CollectionUtils.isEmpty(matchWifiNetworkKeys)
-                            ? null : matchWifiNetworkKeys[0]);
+        if (CollectionUtils.isEmpty(template.mMatchSubscriberIds)) return template;
+
+        for (String[] merged : mergedList) {
+            if (CollectionUtils.contains(merged, template.mMatchSubscriberIds[0])) {
+                // Requested template subscriber is part of the merge group; return
+                // a template that matches all merged subscribers.
+                final String[] matchWifiNetworkKeys = template.mMatchWifiNetworkKeys;
+                // TODO: Use NetworkTemplate.Builder to build a template after NetworkTemplate
+                // could handle incompatible subscriberIds. See b/217805241.
+                return new NetworkTemplate(template.mMatchRule, merged,
+                        CollectionUtils.isEmpty(matchWifiNetworkKeys)
+                                ? new String[0] : new String[] { matchWifiNetworkKeys[0] },
+                        (template.mMatchRule == MATCH_MOBILE
+                                || template.mMatchRule == MATCH_CARRIER)
+                                ? METERED_YES : METERED_ALL,
+                        ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL);
+            }
         }
 
         return template;
@@ -979,10 +1170,7 @@
          * @param matchRule the target match rule to be checked.
          */
         private static void assertRequestableMatchRule(final int matchRule) {
-            if (!isKnownMatchRule(matchRule)
-                    || matchRule == MATCH_PROXY
-                    || matchRule == MATCH_MOBILE_WILDCARD
-                    || matchRule == MATCH_WIFI_WILDCARD) {
+            if (!isKnownMatchRule(matchRule) || matchRule == MATCH_PROXY) {
                 throw new IllegalArgumentException("Invalid match rule: "
                         + getMatchRuleName(matchRule));
             }
@@ -1003,20 +1191,6 @@
         }
 
         /**
-         * For backward compatibility, deduce match rule to a wildcard match rule
-         * if the Subscriber Ids are empty.
-         */
-        private int getWildcardDeducedMatchRule() {
-            if (mMatchRule == MATCH_MOBILE && mMatchSubscriberIds.isEmpty()) {
-                return MATCH_MOBILE_WILDCARD;
-            } else if (mMatchRule == MATCH_WIFI && mMatchSubscriberIds.isEmpty()
-                    && mMatchWifiNetworkKeys.isEmpty()) {
-                return MATCH_WIFI_WILDCARD;
-            }
-            return mMatchRule;
-        }
-
-        /**
          * Builds the instance of the NetworkTemplate.
          *
          * @return the built instance of NetworkTemplate.
@@ -1024,14 +1198,10 @@
         @NonNull
         public NetworkTemplate build() {
             assertRequestableParameters();
-            final int subscriberIdMatchRule = mMatchSubscriberIds.isEmpty()
-                    ? NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL
-                    : NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT;
-            return new NetworkTemplate(getWildcardDeducedMatchRule(),
-                    mMatchSubscriberIds.isEmpty() ? null : mMatchSubscriberIds.iterator().next(),
+            return new NetworkTemplate(mMatchRule,
                     mMatchSubscriberIds.toArray(new String[0]),
                     mMatchWifiNetworkKeys.toArray(new String[0]), mMetered, mRoaming,
-                    mDefaultNetwork, mRatType, mOemManaged, subscriberIdMatchRule);
+                    mDefaultNetwork, mRatType, mOemManaged);
         }
     }
 }
diff --git a/framework-t/src/android/net/nsd/INsdManager.aidl b/framework-t/src/android/net/nsd/INsdManager.aidl
index 89e9cdb..9d14b1a 100644
--- a/framework-t/src/android/net/nsd/INsdManager.aidl
+++ b/framework-t/src/android/net/nsd/INsdManager.aidl
@@ -26,5 +26,5 @@
  * {@hide}
  */
 interface INsdManager {
-    INsdServiceConnector connect(INsdManagerCallback cb);
+    INsdServiceConnector connect(INsdManagerCallback cb, boolean useJavaBackend);
 }
diff --git a/framework-t/src/android/net/nsd/INsdManagerCallback.aidl b/framework-t/src/android/net/nsd/INsdManagerCallback.aidl
index 1a262ec..d89bfa9 100644
--- a/framework-t/src/android/net/nsd/INsdManagerCallback.aidl
+++ b/framework-t/src/android/net/nsd/INsdManagerCallback.aidl
@@ -36,4 +36,10 @@
     void onUnregisterServiceSucceeded(int listenerKey);
     void onResolveServiceFailed(int listenerKey, int error);
     void onResolveServiceSucceeded(int listenerKey, in NsdServiceInfo info);
+    void onStopResolutionFailed(int listenerKey, int error);
+    void onStopResolutionSucceeded(int listenerKey);
+    void onServiceInfoCallbackRegistrationFailed(int listenerKey, int error);
+    void onServiceUpdated(int listenerKey, in NsdServiceInfo info);
+    void onServiceUpdatedLost(int listenerKey);
+    void onServiceInfoCallbackUnregistered(int listenerKey);
 }
diff --git a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
index b06ae55..5533154 100644
--- a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
+++ b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
@@ -32,4 +32,7 @@
     void stopDiscovery(int listenerKey);
     void resolveService(int listenerKey, in NsdServiceInfo serviceInfo);
     void startDaemon();
+    void stopResolution(int listenerKey);
+    void registerServiceInfoCallback(int listenerKey, in NsdServiceInfo serviceInfo);
+    void unregisterServiceInfoCallback(int listenerKey);
 }
\ No newline at end of file
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index fb3b1d6..2930cbd 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -16,6 +16,10 @@
 
 package android.net.nsd;
 
+import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_PLATFORM_MDNS_BACKEND;
+import static android.net.connectivity.ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER;
+
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -23,8 +27,6 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemService;
 import android.app.compat.CompatChanges;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
@@ -44,6 +46,8 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -134,28 +138,6 @@
     private static final boolean DBG = false;
 
     /**
-     * When enabled, apps targeting < Android 12 are considered legacy for
-     * the NSD native daemon.
-     * The platform will only keep the daemon running as long as there are
-     * any legacy apps connected.
-     *
-     * After Android 12, direct communication with the native daemon might not work since the native
-     * daemon won't always stay alive. Using the NSD APIs from NsdManager as the replacement is
-     * recommended.
-     * Another alternative could be bundling your own mdns solutions instead of
-     * depending on the system mdns native daemon.
-     *
-     * This compatibility change applies to Android 13 and later only. To toggle behavior on
-     * Android 12 and Android 12L, use RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS.
-     *
-     * @hide
-     */
-    @ChangeId
-    @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
-    // This was a platform change ID with value 191844585L before T
-    public static final long RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER = 235355681L;
-
-    /**
      * Broadcast intent action to indicate whether network service discovery is
      * enabled or disabled. An extra {@link #EXTRA_NSD_STATE} provides the state
      * information as int.
@@ -230,7 +212,6 @@
 
     /** @hide */
     public static final int DAEMON_CLEANUP                          = 18;
-
     /** @hide */
     public static final int DAEMON_STARTUP                          = 19;
 
@@ -242,6 +223,30 @@
     /** @hide */
     public static final int UNREGISTER_CLIENT                       = 22;
 
+    /** @hide */
+    public static final int MDNS_DISCOVERY_MANAGER_EVENT            = 23;
+
+    /** @hide */
+    public static final int STOP_RESOLUTION                         = 24;
+    /** @hide */
+    public static final int STOP_RESOLUTION_FAILED                  = 25;
+    /** @hide */
+    public static final int STOP_RESOLUTION_SUCCEEDED               = 26;
+
+    /** @hide */
+    public static final int REGISTER_SERVICE_CALLBACK               = 27;
+    /** @hide */
+    public static final int REGISTER_SERVICE_CALLBACK_FAILED        = 28;
+    /** @hide */
+    public static final int SERVICE_UPDATED                         = 29;
+    /** @hide */
+    public static final int SERVICE_UPDATED_LOST                    = 30;
+
+    /** @hide */
+    public static final int UNREGISTER_SERVICE_CALLBACK             = 31;
+    /** @hide */
+    public static final int UNREGISTER_SERVICE_CALLBACK_SUCCEEDED   = 32;
+
     /** Dns based service discovery protocol */
     public static final int PROTOCOL_DNS_SD = 0x0001;
 
@@ -267,6 +272,18 @@
         EVENT_NAMES.put(DAEMON_CLEANUP, "DAEMON_CLEANUP");
         EVENT_NAMES.put(DAEMON_STARTUP, "DAEMON_STARTUP");
         EVENT_NAMES.put(MDNS_SERVICE_EVENT, "MDNS_SERVICE_EVENT");
+        EVENT_NAMES.put(STOP_RESOLUTION, "STOP_RESOLUTION");
+        EVENT_NAMES.put(STOP_RESOLUTION_FAILED, "STOP_RESOLUTION_FAILED");
+        EVENT_NAMES.put(STOP_RESOLUTION_SUCCEEDED, "STOP_RESOLUTION_SUCCEEDED");
+        EVENT_NAMES.put(REGISTER_SERVICE_CALLBACK, "REGISTER_SERVICE_CALLBACK");
+        EVENT_NAMES.put(REGISTER_SERVICE_CALLBACK_FAILED, "REGISTER_SERVICE_CALLBACK_FAILED");
+        EVENT_NAMES.put(SERVICE_UPDATED, "SERVICE_UPDATED");
+        EVENT_NAMES.put(UNREGISTER_SERVICE_CALLBACK, "UNREGISTER_SERVICE_CALLBACK");
+        EVENT_NAMES.put(UNREGISTER_SERVICE_CALLBACK_SUCCEEDED,
+                "UNREGISTER_SERVICE_CALLBACK_SUCCEEDED");
+        EVENT_NAMES.put(MDNS_DISCOVERY_MANAGER_EVENT, "MDNS_DISCOVERY_MANAGER_EVENT");
+        EVENT_NAMES.put(REGISTER_CLIENT, "REGISTER_CLIENT");
+        EVENT_NAMES.put(UNREGISTER_CLIENT, "UNREGISTER_CLIENT");
     }
 
     /** @hide */
@@ -412,6 +429,10 @@
             private final DiscoveryListener mWrapped;
             private final Executor mWrappedExecutor;
             private final ArraySet<TrackedNsdInfo> mFoundInfo = new ArraySet<>();
+            // When this flag is set to true, no further service found or lost callbacks should be
+            // handled. This flag indicates that the network for this DelegatingDiscoveryListener is
+            // lost, and any further callbacks would be redundant.
+            private boolean mAllServicesLost = false;
 
             private DelegatingDiscoveryListener(Network network, DiscoveryListener listener,
                     Executor executor) {
@@ -428,6 +449,7 @@
                     serviceInfo.setNetwork(mNetwork);
                     mWrappedExecutor.execute(() -> mWrapped.onServiceLost(serviceInfo));
                 }
+                mAllServicesLost = true;
             }
 
             @Override
@@ -469,12 +491,22 @@
 
             @Override
             public void onServiceFound(NsdServiceInfo serviceInfo) {
+                if (mAllServicesLost) {
+                    // This DelegatingDiscoveryListener no longer has a network connection. Ignore
+                    // the callback.
+                    return;
+                }
                 mFoundInfo.add(new TrackedNsdInfo(serviceInfo));
                 mWrappedExecutor.execute(() -> mWrapped.onServiceFound(serviceInfo));
             }
 
             @Override
             public void onServiceLost(NsdServiceInfo serviceInfo) {
+                if (mAllServicesLost) {
+                    // This DelegatingDiscoveryListener no longer has a network connection. Ignore
+                    // the callback.
+                    return;
+                }
                 mFoundInfo.remove(new TrackedNsdInfo(serviceInfo));
                 mWrappedExecutor.execute(() -> mWrapped.onServiceLost(serviceInfo));
             }
@@ -497,7 +529,8 @@
         mHandler = new ServiceHandler(t.getLooper());
 
         try {
-            mService = service.connect(new NsdCallbackImpl(mHandler));
+            mService = service.connect(new NsdCallbackImpl(mHandler), CompatChanges.isChangeEnabled(
+                    ENABLE_PLATFORM_MDNS_BACKEND));
         } catch (RemoteException e) {
             throw new RuntimeException("Failed to connect to NsdService");
         }
@@ -592,6 +625,36 @@
         public void onResolveServiceSucceeded(int listenerKey, NsdServiceInfo info) {
             sendInfo(RESOLVE_SERVICE_SUCCEEDED, listenerKey, info);
         }
+
+        @Override
+        public void onStopResolutionFailed(int listenerKey, int error) {
+            sendError(STOP_RESOLUTION_FAILED, listenerKey, error);
+        }
+
+        @Override
+        public void onStopResolutionSucceeded(int listenerKey) {
+            sendNoArg(STOP_RESOLUTION_SUCCEEDED, listenerKey);
+        }
+
+        @Override
+        public void onServiceInfoCallbackRegistrationFailed(int listenerKey, int error) {
+            sendError(REGISTER_SERVICE_CALLBACK_FAILED, listenerKey, error);
+        }
+
+        @Override
+        public void onServiceUpdated(int listenerKey, NsdServiceInfo info) {
+            sendInfo(SERVICE_UPDATED, listenerKey, info);
+        }
+
+        @Override
+        public void onServiceUpdatedLost(int listenerKey) {
+            sendNoArg(SERVICE_UPDATED_LOST, listenerKey);
+        }
+
+        @Override
+        public void onServiceInfoCallbackUnregistered(int listenerKey) {
+            sendNoArg(UNREGISTER_SERVICE_CALLBACK_SUCCEEDED, listenerKey);
+        }
     }
 
     /**
@@ -615,6 +678,37 @@
      */
     public static final int FAILURE_MAX_LIMIT                   = 4;
 
+    /**
+     * Indicates that the stop operation failed because it is not running.
+     * This failure is passed with {@link ResolveListener#onStopResolutionFailed}.
+     */
+    public static final int FAILURE_OPERATION_NOT_RUNNING       = 5;
+
+    /**
+     * Indicates that the service has failed to resolve because of bad parameters.
+     *
+     * This failure is passed with
+     * {@link ServiceInfoCallback#onServiceInfoCallbackRegistrationFailed}.
+     */
+    public static final int FAILURE_BAD_PARAMETERS              = 6;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            FAILURE_OPERATION_NOT_RUNNING,
+    })
+    public @interface StopOperationFailureCode {
+    }
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            FAILURE_ALREADY_ACTIVE,
+            FAILURE_BAD_PARAMETERS,
+    })
+    public @interface ResolutionFailureCode {
+    }
+
     /** Interface for callback invocation for service discovery */
     public interface DiscoveryListener {
 
@@ -643,12 +737,97 @@
         public void onServiceUnregistered(NsdServiceInfo serviceInfo);
     }
 
-    /** Interface for callback invocation for service resolution */
+    /**
+     * Callback for use with {@link NsdManager#resolveService} to resolve the service info and use
+     * with {@link NsdManager#stopServiceResolution} to stop resolution.
+     */
     public interface ResolveListener {
 
-        public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode);
+        /**
+         * Called on the internal thread or with an executor passed to
+         * {@link NsdManager#resolveService} to report the resolution was failed with an error.
+         *
+         * A resolution operation would call either onServiceResolved or onResolveFailed once based
+         * on the result.
+         */
+        void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode);
 
-        public void onServiceResolved(NsdServiceInfo serviceInfo);
+        /**
+         * Called on the internal thread or with an executor passed to
+         * {@link NsdManager#resolveService} to report the resolved service info.
+         *
+         * A resolution operation would call either onServiceResolved or onResolveFailed once based
+         * on the result.
+         */
+        void onServiceResolved(NsdServiceInfo serviceInfo);
+
+        /**
+         * Called on the internal thread or with an executor passed to
+         * {@link NsdManager#resolveService} to report the resolution was stopped.
+         *
+         * A stop resolution operation would call either onResolutionStopped or
+         * onStopResolutionFailed once based on the result.
+         */
+        default void onResolutionStopped(@NonNull NsdServiceInfo serviceInfo) { }
+
+        /**
+         * Called once on the internal thread or with an executor passed to
+         * {@link NsdManager#resolveService} to report that stopping resolution failed with an
+         * error.
+         *
+         * A stop resolution operation would call either onResolutionStopped or
+         * onStopResolutionFailed once based on the result.
+         */
+        default void onStopResolutionFailed(@NonNull NsdServiceInfo serviceInfo,
+                @StopOperationFailureCode int errorCode) { }
+    }
+
+    /**
+     * Callback to listen to service info updates.
+     *
+     * For use with {@link NsdManager#registerServiceInfoCallback} to register, and with
+     * {@link NsdManager#unregisterServiceInfoCallback} to stop listening.
+     */
+    public interface ServiceInfoCallback {
+
+        /**
+         * Reports that registering the callback failed with an error.
+         *
+         * Called on the executor passed to {@link NsdManager#registerServiceInfoCallback}.
+         *
+         * onServiceInfoCallbackRegistrationFailed will be called exactly once when the callback
+         * could not be registered. No other callback will be sent in that case.
+         */
+        void onServiceInfoCallbackRegistrationFailed(@ResolutionFailureCode int errorCode);
+
+        /**
+         * Reports updated service info.
+         *
+         * Called on the executor passed to {@link NsdManager#registerServiceInfoCallback}. Any
+         * service updates will be notified via this callback until
+         * {@link NsdManager#unregisterServiceInfoCallback} is called. This will only be called once
+         * the service is found, so may never be called if the service is never present.
+         */
+        void onServiceUpdated(@NonNull NsdServiceInfo serviceInfo);
+
+        /**
+         * Reports when the service that this callback listens to becomes unavailable.
+         *
+         * Called on the executor passed to {@link NsdManager#registerServiceInfoCallback}. The
+         * service may become available again, in which case {@link #onServiceUpdated} will be
+         * called.
+         */
+        void onServiceLost();
+
+        /**
+         * Reports that service info updates have stopped.
+         *
+         * Called on the executor passed to {@link NsdManager#registerServiceInfoCallback}.
+         *
+         * A callback unregistration operation will call onServiceInfoCallbackUnregistered
+         * once. After this, the callback may be reused.
+         */
+        void onServiceInfoCallbackUnregistered();
     }
 
     @VisibleForTesting
@@ -741,6 +920,33 @@
                     executor.execute(() -> ((ResolveListener) listener).onServiceResolved(
                             (NsdServiceInfo) obj));
                     break;
+                case STOP_RESOLUTION_FAILED:
+                    removeListener(key);
+                    executor.execute(() -> ((ResolveListener) listener).onStopResolutionFailed(
+                            ns, errorCode));
+                    break;
+                case STOP_RESOLUTION_SUCCEEDED:
+                    removeListener(key);
+                    executor.execute(() -> ((ResolveListener) listener).onResolutionStopped(
+                            ns));
+                    break;
+                case REGISTER_SERVICE_CALLBACK_FAILED:
+                    removeListener(key);
+                    executor.execute(() -> ((ServiceInfoCallback) listener)
+                            .onServiceInfoCallbackRegistrationFailed(errorCode));
+                    break;
+                case SERVICE_UPDATED:
+                    executor.execute(() -> ((ServiceInfoCallback) listener)
+                            .onServiceUpdated((NsdServiceInfo) obj));
+                    break;
+                case SERVICE_UPDATED_LOST:
+                    executor.execute(() -> ((ServiceInfoCallback) listener).onServiceLost());
+                    break;
+                case UNREGISTER_SERVICE_CALLBACK_SUCCEEDED:
+                    removeListener(key);
+                    executor.execute(() -> ((ServiceInfoCallback) listener)
+                            .onServiceInfoCallbackUnregistered());
+                    break;
                 default:
                     Log.d(TAG, "Ignored " + message);
                     break;
@@ -1052,7 +1258,14 @@
      * @param serviceInfo service to be resolved
      * @param listener to receive callback upon success or failure. Cannot be null.
      * Cannot be in use for an active service resolution.
+     *
+     * @deprecated the returned ServiceInfo may get stale at any time after resolution, including
+     * immediately after the callback is called, and may not contain some service information that
+     * could be delivered later, like additional host addresses. Prefer using
+     * {@link #registerServiceInfoCallback}, which will keep the application up-to-date with the
+     * state of the service.
      */
+    @Deprecated
     public void resolveService(NsdServiceInfo serviceInfo, ResolveListener listener) {
         resolveService(serviceInfo, Runnable::run, listener);
     }
@@ -1064,7 +1277,14 @@
      * @param serviceInfo service to be resolved
      * @param executor Executor to run listener callbacks with
      * @param listener to receive callback upon success or failure.
+     *
+     * @deprecated the returned ServiceInfo may get stale at any time after resolution, including
+     * immediately after the callback is called, and may not contain some service information that
+     * could be delivered later, like additional host addresses. Prefer using
+     * {@link #registerServiceInfoCallback}, which will keep the application up-to-date with the
+     * state of the service.
      */
+    @Deprecated
     public void resolveService(@NonNull NsdServiceInfo serviceInfo,
             @NonNull Executor executor, @NonNull ResolveListener listener) {
         checkServiceInfo(serviceInfo);
@@ -1076,6 +1296,85 @@
         }
     }
 
+    /**
+     * Stop service resolution initiated with {@link #resolveService}.
+     *
+     * A successful stop is notified with a call to {@link ResolveListener#onResolutionStopped}.
+     *
+     * <p> Upon failure to stop service resolution for example if resolution is done or the
+     * requester stops resolution repeatedly, the application is notified
+     * {@link ResolveListener#onStopResolutionFailed} with {@link #FAILURE_OPERATION_NOT_RUNNING}
+     *
+     * @param listener This should be a listener object that was passed to {@link #resolveService}.
+     *                 It identifies the resolution that should be stopped and notifies of a
+     *                 successful or unsuccessful stop. Throws {@code IllegalArgumentException} if
+     *                 the listener was not passed to resolveService before.
+     */
+    public void stopServiceResolution(@NonNull ResolveListener listener) {
+        int id = getListenerKey(listener);
+        try {
+            mService.stopResolution(id);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Register a callback to listen for updates to a service.
+     *
+     * An application can listen to a service to continuously monitor availability of given service.
+     * The callback methods will be called on the passed executor. And service updates are sent with
+     * continuous calls to {@link ServiceInfoCallback#onServiceUpdated}.
+     *
+     * This is different from {@link #resolveService} which provides one shot service information.
+     *
+     * <p> An application can listen to a service once a time. It needs to cancel the registration
+     * before registering other callbacks. Upon failure to register a callback for example if
+     * it's a duplicated registration, the application is notified through
+     * {@link ServiceInfoCallback#onServiceInfoCallbackRegistrationFailed} with
+     * {@link #FAILURE_BAD_PARAMETERS}.
+     *
+     * @param serviceInfo the service to receive updates for
+     * @param executor Executor to run callbacks with
+     * @param listener to receive callback upon service update
+     */
+    public void registerServiceInfoCallback(@NonNull NsdServiceInfo serviceInfo,
+            @NonNull Executor executor, @NonNull ServiceInfoCallback listener) {
+        checkServiceInfo(serviceInfo);
+        int key = putListener(listener, executor, serviceInfo);
+        try {
+            mService.registerServiceInfoCallback(key, serviceInfo);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregister a callback registered with {@link #registerServiceInfoCallback}.
+     *
+     * A successful unregistration is notified with a call to
+     * {@link ServiceInfoCallback#onServiceInfoCallbackUnregistered}. The same callback can only be
+     * reused after this is called.
+     *
+     * <p>If the callback is not already registered, this will throw with
+     * {@link IllegalArgumentException}.
+     *
+     * @param listener This should be a listener object that was passed to
+     *                 {@link #registerServiceInfoCallback}. It identifies the registration that
+     *                 should be unregistered and notifies of a successful or unsuccessful stop.
+     *                 Throws {@code IllegalArgumentException} if the listener was not passed to
+     *                 {@link #registerServiceInfoCallback} before.
+     */
+    public void unregisterServiceInfoCallback(@NonNull ServiceInfoCallback listener) {
+        // Will throw IllegalArgumentException if the listener is not known
+        int id = getListenerKey(listener);
+        try {
+            mService.unregisterServiceInfoCallback(id);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     private static void checkListener(Object listener) {
         Objects.requireNonNull(listener, "listener cannot be null");
     }
diff --git a/framework-t/src/android/net/nsd/NsdServiceInfo.java b/framework-t/src/android/net/nsd/NsdServiceInfo.java
index 6438a60..caeecdd 100644
--- a/framework-t/src/android/net/nsd/NsdServiceInfo.java
+++ b/framework-t/src/android/net/nsd/NsdServiceInfo.java
@@ -26,10 +26,14 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.net.module.util.InetAddressUtils;
+
 import java.io.UnsupportedEncodingException;
 import java.net.InetAddress;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -46,7 +50,7 @@
 
     private final ArrayMap<String, byte[]> mTxtRecord = new ArrayMap<>();
 
-    private InetAddress mHost;
+    private final List<InetAddress> mHostAddresses = new ArrayList<>();
 
     private int mPort;
 
@@ -84,17 +88,32 @@
         mServiceType = s;
     }
 
-    /** Get the host address. The host address is valid for a resolved service. */
+    /**
+     * Get the host address. The host address is valid for a resolved service.
+     *
+     * @deprecated Use {@link #getHostAddresses()} to get the entire list of addresses for the host.
+     */
+    @Deprecated
     public InetAddress getHost() {
-        return mHost;
+        return mHostAddresses.size() == 0 ? null : mHostAddresses.get(0);
     }
 
-    /** Set the host address */
+    /**
+     * Set the host address
+     *
+     * @deprecated Use {@link #setHostAddresses(List)} to set multiple addresses for the host.
+     */
+    @Deprecated
     public void setHost(InetAddress s) {
-        mHost = s;
+        setHostAddresses(Collections.singletonList(s));
     }
 
-    /** Get port number. The port number is valid for a resolved service. */
+    /**
+     * Get port number. The port number is valid for a resolved service.
+     *
+     * The port is valid for all addresses.
+     * @see #getHostAddresses()
+     */
     public int getPort() {
         return mPort;
     }
@@ -105,6 +124,24 @@
     }
 
     /**
+     * Get the host addresses.
+     *
+     * All host addresses are valid for the resolved service.
+     * All addresses share the same port
+     * @see #getPort()
+     */
+    @NonNull
+    public List<InetAddress> getHostAddresses() {
+        return new ArrayList<>(mHostAddresses);
+    }
+
+    /** Set the host addresses */
+    public void setHostAddresses(@NonNull List<InetAddress> addresses) {
+        mHostAddresses.clear();
+        mHostAddresses.addAll(addresses);
+    }
+
+    /**
      * Unpack txt information from a base-64 encoded byte array.
      *
      * @param txtRecordsRawBytes The raw base64 encoded byte array.
@@ -359,7 +396,7 @@
         StringBuilder sb = new StringBuilder();
         sb.append("name: ").append(mServiceName)
                 .append(", type: ").append(mServiceType)
-                .append(", host: ").append(mHost)
+                .append(", hostAddresses: ").append(TextUtils.join(", ", mHostAddresses))
                 .append(", port: ").append(mPort)
                 .append(", network: ").append(mNetwork);
 
@@ -377,12 +414,6 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(mServiceName);
         dest.writeString(mServiceType);
-        if (mHost != null) {
-            dest.writeInt(1);
-            dest.writeByteArray(mHost.getAddress());
-        } else {
-            dest.writeInt(0);
-        }
         dest.writeInt(mPort);
 
         // TXT record key/value pairs.
@@ -401,6 +432,10 @@
 
         dest.writeParcelable(mNetwork, 0);
         dest.writeInt(mInterfaceIndex);
+        dest.writeInt(mHostAddresses.size());
+        for (InetAddress address : mHostAddresses) {
+            InetAddressUtils.parcelInetAddress(dest, address, flags);
+        }
     }
 
     /** Implement the Parcelable interface */
@@ -410,13 +445,6 @@
                 NsdServiceInfo info = new NsdServiceInfo();
                 info.mServiceName = in.readString();
                 info.mServiceType = in.readString();
-
-                if (in.readInt() == 1) {
-                    try {
-                        info.mHost = InetAddress.getByAddress(in.createByteArray());
-                    } catch (java.net.UnknownHostException e) {}
-                }
-
                 info.mPort = in.readInt();
 
                 // TXT record key/value pairs.
@@ -432,6 +460,10 @@
                 }
                 info.mNetwork = in.readParcelable(null, Network.class);
                 info.mInterfaceIndex = in.readInt();
+                int size = in.readInt();
+                for (int i = 0; i < size; i++) {
+                    info.mHostAddresses.add(InetAddressUtils.unparcelInetAddress(in));
+                }
                 return info;
             }
 
diff --git a/framework/Android.bp b/framework/Android.bp
index 485961c..123f02a 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -45,14 +45,12 @@
 // TODO: use a java_library in the bootclasspath instead
 filegroup {
     name: "framework-connectivity-sources",
+    defaults: ["framework-sources-module-defaults"],
     srcs: [
         ":framework-connectivity-internal-sources",
         ":framework-connectivity-aidl-export-sources",
     ],
-    visibility: [
-        "//frameworks/base",
-        "//packages/modules/Connectivity:__subpackages__",
-    ],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
 }
 
 java_defaults {
@@ -80,7 +78,9 @@
         "framework-connectivity-t.stubs.module_lib",
     ],
     impl_only_libs: [
-        "framework-tethering.stubs.module_lib",
+        // TODO: figure out why just using "framework-tethering" uses the stubs, even though both
+        // framework-connectivity and framework-tethering are in the same APEX.
+        "framework-tethering.impl",
         "framework-wifi.stubs.module_lib",
         "net-utils-device-common",
     ],
@@ -105,21 +105,41 @@
 
 java_library {
     name: "framework-connectivity-pre-jarjar",
-    defaults: ["framework-connectivity-defaults"],
+    defaults: [
+        "framework-connectivity-defaults",
+    ],
+    static_libs: [
+        "cronet_aml_api_java",
+        "cronet_aml_java",
+    ],
     libs: [
         // This cannot be in the defaults clause above because if it were, it would be used
         // to generate the connectivity stubs. That would create a circular dependency
-        // because the tethering stubs depend on the connectivity stubs (e.g.,
+        // because the tethering impl depend on the connectivity stubs (e.g.,
         // TetheringRequest depends on LinkAddress).
-        "framework-tethering.stubs.module_lib",
+        "framework-tethering.impl",
         "framework-wifi.stubs.module_lib",
     ],
     visibility: ["//packages/modules/Connectivity:__subpackages__"]
 }
 
+java_defaults {
+    name: "CronetJavaDefaults",
+    srcs: [":cronet_aml_api_sources"],
+    libs: [
+        "androidx.annotation_annotation",
+    ],
+    impl_only_static_libs: [
+        "cronet_aml_java",
+    ],
+}
+
 java_sdk_library {
     name: "framework-connectivity",
-    defaults: ["framework-connectivity-defaults"],
+    defaults: [
+        "framework-connectivity-defaults",
+        "CronetJavaDefaults",
+    ],
     installable: true,
     jarjar_rules: ":framework-connectivity-jarjar-rules",
     permitted_packages: ["android.net"],
@@ -147,6 +167,7 @@
         "//frameworks/opt/net/ethernet/tests:__subpackages__",
         "//frameworks/opt/telephony/tests/telephonytests",
         "//packages/modules/CaptivePortalLogin/tests",
+        "//packages/modules/Connectivity/Cronet/tests:__subpackages__",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
         "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/IPsec/tests/iketests",
@@ -183,6 +204,7 @@
         "libnativehelper",
     ],
     header_libs: [
+        "bpf_headers",
         "dnsproxyd_protocol_headers",
     ],
     stl: "none",
@@ -243,3 +265,24 @@
         "//packages/modules/Connectivity/service",
     ],
 }
+
+// Library providing limited APIs within the connectivity module, so that R+ components like
+// Tethering have a controlled way to depend on newer components like framework-connectivity that
+// are not loaded on R.
+java_library {
+    name: "connectivity-internal-api-util",
+    sdk_version: "module_current",
+    libs: [
+        "androidx.annotation_annotation",
+        "framework-connectivity.impl",
+    ],
+    jarjar_rules: ":framework-connectivity-jarjar-rules",
+    srcs: [
+        // Files listed here MUST all be annotated with @RequiresApi(Build.VERSION_CODES.TIRAMISU),
+        // so that API checks are enforced for R+ users of this library
+        "src/android/net/connectivity/TiramisuConnectivityInternalApiUtil.java",
+    ],
+    visibility: [
+        "//packages/modules/Connectivity/Tethering:__subpackages__",
+    ],
+}
diff --git a/framework/api/current.txt b/framework/api/current.txt
index 547b7e2..6860c3c 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -360,6 +360,7 @@
     field public static final int TRANSPORT_CELLULAR = 0; // 0x0
     field public static final int TRANSPORT_ETHERNET = 3; // 0x3
     field public static final int TRANSPORT_LOWPAN = 6; // 0x6
+    field public static final int TRANSPORT_THREAD = 9; // 0x9
     field public static final int TRANSPORT_USB = 8; // 0x8
     field public static final int TRANSPORT_VPN = 4; // 0x4
     field public static final int TRANSPORT_WIFI = 1; // 0x1
@@ -524,3 +525,292 @@
 
 }
 
+package android.net.http {
+
+  public abstract class BidirectionalStream {
+    ctor public BidirectionalStream();
+    method public abstract void cancel();
+    method public abstract void flush();
+    method @NonNull public abstract android.net.http.HeaderBlock getHeaders();
+    method @NonNull public abstract String getHttpMethod();
+    method public abstract int getPriority();
+    method public abstract int getTrafficStatsTag();
+    method public abstract int getTrafficStatsUid();
+    method public abstract boolean hasTrafficStatsTag();
+    method public abstract boolean hasTrafficStatsUid();
+    method public abstract boolean isDelayRequestHeadersUntilFirstFlushEnabled();
+    method public abstract boolean isDone();
+    method public abstract void read(@NonNull java.nio.ByteBuffer);
+    method public abstract void start();
+    method public abstract void write(@NonNull java.nio.ByteBuffer, boolean);
+    field public static final int STREAM_PRIORITY_HIGHEST = 4; // 0x4
+    field public static final int STREAM_PRIORITY_IDLE = 0; // 0x0
+    field public static final int STREAM_PRIORITY_LOW = 2; // 0x2
+    field public static final int STREAM_PRIORITY_LOWEST = 1; // 0x1
+    field public static final int STREAM_PRIORITY_MEDIUM = 3; // 0x3
+  }
+
+  public abstract static class BidirectionalStream.Builder {
+    ctor public BidirectionalStream.Builder();
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder addHeader(@NonNull String, @NonNull String);
+    method @NonNull public abstract android.net.http.BidirectionalStream build();
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder setDelayRequestHeadersUntilFirstFlushEnabled(boolean);
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder setHttpMethod(@NonNull String);
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder setPriority(int);
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder setTrafficStatsTag(int);
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder setTrafficStatsUid(int);
+  }
+
+  public static interface BidirectionalStream.Callback {
+    method public void onCanceled(@NonNull android.net.http.BidirectionalStream, @Nullable android.net.http.UrlResponseInfo);
+    method public void onFailed(@NonNull android.net.http.BidirectionalStream, @Nullable android.net.http.UrlResponseInfo, @NonNull android.net.http.HttpException);
+    method public void onReadCompleted(@NonNull android.net.http.BidirectionalStream, @NonNull android.net.http.UrlResponseInfo, @NonNull java.nio.ByteBuffer, boolean);
+    method public void onResponseHeadersReceived(@NonNull android.net.http.BidirectionalStream, @NonNull android.net.http.UrlResponseInfo);
+    method public void onResponseTrailersReceived(@NonNull android.net.http.BidirectionalStream, @NonNull android.net.http.UrlResponseInfo, @NonNull android.net.http.HeaderBlock);
+    method public void onStreamReady(@NonNull android.net.http.BidirectionalStream);
+    method public void onSucceeded(@NonNull android.net.http.BidirectionalStream, @NonNull android.net.http.UrlResponseInfo);
+    method public void onWriteCompleted(@NonNull android.net.http.BidirectionalStream, @NonNull android.net.http.UrlResponseInfo, @NonNull java.nio.ByteBuffer, boolean);
+  }
+
+  public abstract class CallbackException extends android.net.http.HttpException {
+    ctor protected CallbackException(@Nullable String, @Nullable Throwable);
+  }
+
+  public class ConnectionMigrationOptions {
+    method public int getAllowNonDefaultNetworkUsage();
+    method public int getDefaultNetworkMigration();
+    method public int getPathDegradationMigration();
+    field public static final int MIGRATION_OPTION_DISABLED = 2; // 0x2
+    field public static final int MIGRATION_OPTION_ENABLED = 1; // 0x1
+    field public static final int MIGRATION_OPTION_UNSPECIFIED = 0; // 0x0
+  }
+
+  public static final class ConnectionMigrationOptions.Builder {
+    ctor public ConnectionMigrationOptions.Builder();
+    method @NonNull public android.net.http.ConnectionMigrationOptions build();
+    method @NonNull public android.net.http.ConnectionMigrationOptions.Builder setAllowNonDefaultNetworkUsage(int);
+    method @NonNull public android.net.http.ConnectionMigrationOptions.Builder setDefaultNetworkMigration(int);
+    method @NonNull public android.net.http.ConnectionMigrationOptions.Builder setPathDegradationMigration(int);
+  }
+
+  public final class DnsOptions {
+    method public int getPersistHostCache();
+    method @Nullable public java.time.Duration getPersistHostCachePeriod();
+    method public int getPreestablishConnectionsToStaleDnsResults();
+    method public int getStaleDns();
+    method @Nullable public android.net.http.DnsOptions.StaleDnsOptions getStaleDnsOptions();
+    method public int getUseHttpStackDnsResolver();
+    field public static final int DNS_OPTION_DISABLED = 2; // 0x2
+    field public static final int DNS_OPTION_ENABLED = 1; // 0x1
+    field public static final int DNS_OPTION_UNSPECIFIED = 0; // 0x0
+  }
+
+  public static final class DnsOptions.Builder {
+    ctor public DnsOptions.Builder();
+    method @NonNull public android.net.http.DnsOptions build();
+    method @NonNull public android.net.http.DnsOptions.Builder setPersistHostCache(int);
+    method @NonNull public android.net.http.DnsOptions.Builder setPersistHostCachePeriod(@NonNull java.time.Duration);
+    method @NonNull public android.net.http.DnsOptions.Builder setPreestablishConnectionsToStaleDnsResults(int);
+    method @NonNull public android.net.http.DnsOptions.Builder setStaleDns(int);
+    method @NonNull public android.net.http.DnsOptions.Builder setStaleDnsOptions(@NonNull android.net.http.DnsOptions.StaleDnsOptions);
+    method @NonNull public android.net.http.DnsOptions.Builder setUseHttpStackDnsResolver(int);
+  }
+
+  public static class DnsOptions.StaleDnsOptions {
+    method public int getAllowCrossNetworkUsage();
+    method @Nullable public java.time.Duration getFreshLookupTimeout();
+    method @Nullable public java.time.Duration getMaxExpiredDelay();
+    method public int getUseStaleOnNameNotResolved();
+  }
+
+  public static final class DnsOptions.StaleDnsOptions.Builder {
+    ctor public DnsOptions.StaleDnsOptions.Builder();
+    method @NonNull public android.net.http.DnsOptions.StaleDnsOptions build();
+    method @NonNull public android.net.http.DnsOptions.StaleDnsOptions.Builder setAllowCrossNetworkUsage(int);
+    method @NonNull public android.net.http.DnsOptions.StaleDnsOptions.Builder setFreshLookupTimeout(@NonNull java.time.Duration);
+    method @NonNull public android.net.http.DnsOptions.StaleDnsOptions.Builder setMaxExpiredDelay(@NonNull java.time.Duration);
+    method @NonNull public android.net.http.DnsOptions.StaleDnsOptions.Builder setUseStaleOnNameNotResolved(int);
+  }
+
+  public abstract class HeaderBlock {
+    ctor public HeaderBlock();
+    method @NonNull public abstract java.util.List<java.util.Map.Entry<java.lang.String,java.lang.String>> getAsList();
+    method @NonNull public abstract java.util.Map<java.lang.String,java.util.List<java.lang.String>> getAsMap();
+  }
+
+  public abstract class HttpEngine {
+    method public void bindToNetwork(@Nullable android.net.Network);
+    method @NonNull public abstract java.net.URLStreamHandlerFactory createUrlStreamHandlerFactory();
+    method @NonNull public static String getVersionString();
+    method @NonNull public abstract android.net.http.BidirectionalStream.Builder newBidirectionalStreamBuilder(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.http.BidirectionalStream.Callback);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder newUrlRequestBuilder(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.http.UrlRequest.Callback);
+    method @NonNull public abstract java.net.URLConnection openConnection(@NonNull java.net.URL) throws java.io.IOException;
+    method public abstract void shutdown();
+  }
+
+  public static class HttpEngine.Builder {
+    ctor public HttpEngine.Builder(@NonNull android.content.Context);
+    method @NonNull public android.net.http.HttpEngine.Builder addPublicKeyPins(@NonNull String, @NonNull java.util.Set<byte[]>, boolean, @NonNull java.time.Instant);
+    method @NonNull public android.net.http.HttpEngine.Builder addQuicHint(@NonNull String, int, int);
+    method @NonNull public android.net.http.HttpEngine build();
+    method @NonNull public String getDefaultUserAgent();
+    method @NonNull public android.net.http.HttpEngine.Builder setConnectionMigrationOptions(@NonNull android.net.http.ConnectionMigrationOptions);
+    method @NonNull public android.net.http.HttpEngine.Builder setDnsOptions(@NonNull android.net.http.DnsOptions);
+    method @NonNull public android.net.http.HttpEngine.Builder setEnableBrotli(boolean);
+    method @NonNull public android.net.http.HttpEngine.Builder setEnableHttp2(boolean);
+    method @NonNull public android.net.http.HttpEngine.Builder setEnableHttpCache(int, long);
+    method @NonNull public android.net.http.HttpEngine.Builder setEnablePublicKeyPinningBypassForLocalTrustAnchors(boolean);
+    method @NonNull public android.net.http.HttpEngine.Builder setEnableQuic(boolean);
+    method @NonNull public android.net.http.HttpEngine.Builder setQuicOptions(@NonNull android.net.http.QuicOptions);
+    method @NonNull public android.net.http.HttpEngine.Builder setStoragePath(@NonNull String);
+    method @NonNull public android.net.http.HttpEngine.Builder setUserAgent(@NonNull String);
+    field public static final int HTTP_CACHE_DISABLED = 0; // 0x0
+    field public static final int HTTP_CACHE_DISK = 3; // 0x3
+    field public static final int HTTP_CACHE_DISK_NO_HTTP = 2; // 0x2
+    field public static final int HTTP_CACHE_IN_MEMORY = 1; // 0x1
+  }
+
+  public class HttpException extends java.io.IOException {
+    ctor public HttpException(@Nullable String, @Nullable Throwable);
+  }
+
+  public final class InlineExecutionProhibitedException extends java.util.concurrent.RejectedExecutionException {
+    ctor public InlineExecutionProhibitedException();
+  }
+
+  public abstract class NetworkException extends android.net.http.HttpException {
+    ctor public NetworkException(@Nullable String, @Nullable Throwable);
+    method public abstract int getErrorCode();
+    method public abstract boolean isImmediatelyRetryable();
+    field public static final int ERROR_ADDRESS_UNREACHABLE = 9; // 0x9
+    field public static final int ERROR_CONNECTION_CLOSED = 5; // 0x5
+    field public static final int ERROR_CONNECTION_REFUSED = 7; // 0x7
+    field public static final int ERROR_CONNECTION_RESET = 8; // 0x8
+    field public static final int ERROR_CONNECTION_TIMED_OUT = 6; // 0x6
+    field public static final int ERROR_HOSTNAME_NOT_RESOLVED = 1; // 0x1
+    field public static final int ERROR_INTERNET_DISCONNECTED = 2; // 0x2
+    field public static final int ERROR_NETWORK_CHANGED = 3; // 0x3
+    field public static final int ERROR_OTHER = 11; // 0xb
+    field public static final int ERROR_QUIC_PROTOCOL_FAILED = 10; // 0xa
+    field public static final int ERROR_TIMED_OUT = 4; // 0x4
+  }
+
+  public abstract class QuicException extends android.net.http.NetworkException {
+    ctor protected QuicException(@Nullable String, @Nullable Throwable);
+  }
+
+  public class QuicOptions {
+    method @NonNull public java.util.Set<java.lang.String> getAllowedQuicHosts();
+    method @Nullable public String getHandshakeUserAgent();
+    method @Nullable public java.time.Duration getIdleConnectionTimeout();
+    method public int getInMemoryServerConfigsCacheSize();
+    method public boolean hasInMemoryServerConfigsCacheSize();
+  }
+
+  public static final class QuicOptions.Builder {
+    ctor public QuicOptions.Builder();
+    method @NonNull public android.net.http.QuicOptions.Builder addAllowedQuicHost(@NonNull String);
+    method @NonNull public android.net.http.QuicOptions build();
+    method @NonNull public android.net.http.QuicOptions.Builder setHandshakeUserAgent(@NonNull String);
+    method @NonNull public android.net.http.QuicOptions.Builder setIdleConnectionTimeout(@NonNull java.time.Duration);
+    method @NonNull public android.net.http.QuicOptions.Builder setInMemoryServerConfigsCacheSize(int);
+  }
+
+  public abstract class UploadDataProvider implements java.io.Closeable {
+    ctor public UploadDataProvider();
+    method public void close() throws java.io.IOException;
+    method public abstract long getLength() throws java.io.IOException;
+    method public abstract void read(@NonNull android.net.http.UploadDataSink, @NonNull java.nio.ByteBuffer) throws java.io.IOException;
+    method public abstract void rewind(@NonNull android.net.http.UploadDataSink) throws java.io.IOException;
+  }
+
+  public abstract class UploadDataSink {
+    ctor public UploadDataSink();
+    method public abstract void onReadError(@NonNull Exception);
+    method public abstract void onReadSucceeded(boolean);
+    method public abstract void onRewindError(@NonNull Exception);
+    method public abstract void onRewindSucceeded();
+  }
+
+  public abstract class UrlRequest {
+    method public abstract void cancel();
+    method public abstract void followRedirect();
+    method @NonNull public abstract android.net.http.HeaderBlock getHeaders();
+    method @Nullable public abstract String getHttpMethod();
+    method public abstract int getPriority();
+    method public abstract void getStatus(@NonNull android.net.http.UrlRequest.StatusListener);
+    method public abstract int getTrafficStatsTag();
+    method public abstract int getTrafficStatsUid();
+    method public abstract boolean hasTrafficStatsTag();
+    method public abstract boolean hasTrafficStatsUid();
+    method public abstract boolean isCacheDisabled();
+    method public abstract boolean isDirectExecutorAllowed();
+    method public abstract boolean isDone();
+    method public abstract void read(@NonNull java.nio.ByteBuffer);
+    method public abstract void start();
+    field public static final int REQUEST_PRIORITY_HIGHEST = 4; // 0x4
+    field public static final int REQUEST_PRIORITY_IDLE = 0; // 0x0
+    field public static final int REQUEST_PRIORITY_LOW = 2; // 0x2
+    field public static final int REQUEST_PRIORITY_LOWEST = 1; // 0x1
+    field public static final int REQUEST_PRIORITY_MEDIUM = 3; // 0x3
+  }
+
+  public abstract static class UrlRequest.Builder {
+    method @NonNull public abstract android.net.http.UrlRequest.Builder addHeader(@NonNull String, @NonNull String);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder bindToNetwork(@Nullable android.net.Network);
+    method @NonNull public abstract android.net.http.UrlRequest build();
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setCacheDisabled(boolean);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setDirectExecutorAllowed(boolean);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setHttpMethod(@NonNull String);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setPriority(int);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setTrafficStatsTag(int);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setTrafficStatsUid(int);
+    method @NonNull public abstract android.net.http.UrlRequest.Builder setUploadDataProvider(@NonNull android.net.http.UploadDataProvider, @NonNull java.util.concurrent.Executor);
+  }
+
+  public static interface UrlRequest.Callback {
+    method public void onCanceled(@NonNull android.net.http.UrlRequest, @Nullable android.net.http.UrlResponseInfo);
+    method public void onFailed(@NonNull android.net.http.UrlRequest, @Nullable android.net.http.UrlResponseInfo, @NonNull android.net.http.HttpException);
+    method public void onReadCompleted(@NonNull android.net.http.UrlRequest, @NonNull android.net.http.UrlResponseInfo, @NonNull java.nio.ByteBuffer) throws java.lang.Exception;
+    method public void onRedirectReceived(@NonNull android.net.http.UrlRequest, @NonNull android.net.http.UrlResponseInfo, @NonNull String) throws java.lang.Exception;
+    method public void onResponseStarted(@NonNull android.net.http.UrlRequest, @NonNull android.net.http.UrlResponseInfo) throws java.lang.Exception;
+    method public void onSucceeded(@NonNull android.net.http.UrlRequest, @NonNull android.net.http.UrlResponseInfo);
+  }
+
+  public static class UrlRequest.Status {
+    field public static final int CONNECTING = 10; // 0xa
+    field public static final int DOWNLOADING_PAC_FILE = 5; // 0x5
+    field public static final int ESTABLISHING_PROXY_TUNNEL = 8; // 0x8
+    field public static final int IDLE = 0; // 0x0
+    field public static final int INVALID = -1; // 0xffffffff
+    field public static final int READING_RESPONSE = 14; // 0xe
+    field public static final int RESOLVING_HOST = 9; // 0x9
+    field public static final int RESOLVING_HOST_IN_PAC_FILE = 7; // 0x7
+    field public static final int RESOLVING_PROXY_FOR_URL = 6; // 0x6
+    field public static final int SENDING_REQUEST = 12; // 0xc
+    field public static final int SSL_HANDSHAKE = 11; // 0xb
+    field public static final int WAITING_FOR_AVAILABLE_SOCKET = 2; // 0x2
+    field public static final int WAITING_FOR_CACHE = 4; // 0x4
+    field public static final int WAITING_FOR_DELEGATE = 3; // 0x3
+    field public static final int WAITING_FOR_RESPONSE = 13; // 0xd
+    field public static final int WAITING_FOR_STALLED_SOCKET_POOL = 1; // 0x1
+  }
+
+  public static interface UrlRequest.StatusListener {
+    method public void onStatus(int);
+  }
+
+  public abstract class UrlResponseInfo {
+    ctor public UrlResponseInfo();
+    method @NonNull public abstract android.net.http.HeaderBlock getHeaders();
+    method public abstract int getHttpStatusCode();
+    method @NonNull public abstract String getHttpStatusText();
+    method @NonNull public abstract String getNegotiatedProtocol();
+    method public abstract long getReceivedByteCount();
+    method @NonNull public abstract String getUrl();
+    method @NonNull public abstract java.util.List<java.lang.String> getUrlChain();
+    method public abstract boolean wasCached();
+  }
+
+}
+
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 752c347..193bd92 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -15,7 +15,7 @@
     method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.LinkProperties getRedactedLinkPropertiesForPackage(@NonNull android.net.LinkProperties, int, @NonNull String);
     method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.NetworkCapabilities getRedactedNetworkCapabilitiesForPackage(@NonNull android.net.NetworkCapabilities, int, @NonNull String);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackForUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
-    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void removeUidFromMeteredNetworkAllowList(int);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void removeUidFromMeteredNetworkDenyList(int);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void replaceFirewallChain(int, @NonNull int[]);
@@ -31,6 +31,7 @@
     method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreferences(@NonNull android.os.UserHandle, @NonNull java.util.List<android.net.ProfileNetworkPreference>, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setRequireVpnForUids(boolean, @NonNull java.util.Collection<android.util.Range<java.lang.Integer>>);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setUidFirewallRule(int, int, int);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setVpnDefaultForUids(@NonNull String, @NonNull java.util.Collection<android.util.Range<java.lang.Integer>>);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void startCaptivePortalApp(@NonNull android.net.Network);
     method public void systemReady();
@@ -62,6 +63,7 @@
     field public static final int FIREWALL_RULE_DENY = 2; // 0x2
     field public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; // 0x0
     field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1
+    field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING = 3; // 0x3
     field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK = 2; // 0x2
   }
 
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index c7872a0..4a2ed8a 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -470,7 +470,9 @@
   }
 
   public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+    method public final void start(@IntRange(from=0xa, to=0xe10) int, int, @Nullable android.net.Network);
     field public static final int ERROR_NO_SUCH_SLOT = -33; // 0xffffffdf
+    field public static final int FLAG_AUTOMATIC_ON_OFF = 1; // 0x1
     field public static final int SUCCESS = 0; // 0x0
   }
 
@@ -512,10 +514,11 @@
   }
 
   public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
-    ctor public VpnTransportInfo(int, @Nullable String, boolean);
+    ctor public VpnTransportInfo(int, @Nullable String, boolean, boolean);
+    method public boolean areLongLivedTcpConnectionsExpensive();
     method public int describeContents();
-    method public boolean getBypassable();
     method public int getType();
+    method public boolean isBypassable();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.VpnTransportInfo> CREATOR;
   }
diff --git a/framework/cronet_disabled/api/current.txt b/framework/cronet_disabled/api/current.txt
new file mode 100644
index 0000000..672e3e2
--- /dev/null
+++ b/framework/cronet_disabled/api/current.txt
@@ -0,0 +1,527 @@
+// Signature format: 2.0
+package android.net {
+
+  public class CaptivePortal implements android.os.Parcelable {
+    method public int describeContents();
+    method public void ignoreNetwork();
+    method public void reportCaptivePortalDismissed();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortal> CREATOR;
+  }
+
+  public class ConnectivityDiagnosticsManager {
+    method public void registerConnectivityDiagnosticsCallback(@NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback);
+    method public void unregisterConnectivityDiagnosticsCallback(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback);
+  }
+
+  public abstract static class ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback {
+    ctor public ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback();
+    method public void onConnectivityReportAvailable(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityReport);
+    method public void onDataStallSuspected(@NonNull android.net.ConnectivityDiagnosticsManager.DataStallReport);
+    method public void onNetworkConnectivityReported(@NonNull android.net.Network, boolean);
+  }
+
+  public static final class ConnectivityDiagnosticsManager.ConnectivityReport implements android.os.Parcelable {
+    ctor public ConnectivityDiagnosticsManager.ConnectivityReport(@NonNull android.net.Network, long, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle);
+    method public int describeContents();
+    method @NonNull public android.os.PersistableBundle getAdditionalInfo();
+    method @NonNull public android.net.LinkProperties getLinkProperties();
+    method @NonNull public android.net.Network getNetwork();
+    method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities();
+    method public long getReportTimestamp();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.ConnectivityReport> CREATOR;
+    field public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = "networkProbesAttempted";
+    field public static final String KEY_NETWORK_PROBES_SUCCEEDED_BITMASK = "networkProbesSucceeded";
+    field public static final String KEY_NETWORK_VALIDATION_RESULT = "networkValidationResult";
+    field public static final int NETWORK_PROBE_DNS = 4; // 0x4
+    field public static final int NETWORK_PROBE_FALLBACK = 32; // 0x20
+    field public static final int NETWORK_PROBE_HTTP = 8; // 0x8
+    field public static final int NETWORK_PROBE_HTTPS = 16; // 0x10
+    field public static final int NETWORK_PROBE_PRIVATE_DNS = 64; // 0x40
+    field public static final int NETWORK_VALIDATION_RESULT_INVALID = 0; // 0x0
+    field public static final int NETWORK_VALIDATION_RESULT_PARTIALLY_VALID = 2; // 0x2
+    field public static final int NETWORK_VALIDATION_RESULT_SKIPPED = 3; // 0x3
+    field public static final int NETWORK_VALIDATION_RESULT_VALID = 1; // 0x1
+  }
+
+  public static final class ConnectivityDiagnosticsManager.DataStallReport implements android.os.Parcelable {
+    ctor public ConnectivityDiagnosticsManager.DataStallReport(@NonNull android.net.Network, long, int, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle);
+    method public int describeContents();
+    method public int getDetectionMethod();
+    method @NonNull public android.net.LinkProperties getLinkProperties();
+    method @NonNull public android.net.Network getNetwork();
+    method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities();
+    method public long getReportTimestamp();
+    method @NonNull public android.os.PersistableBundle getStallDetails();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.DataStallReport> CREATOR;
+    field public static final int DETECTION_METHOD_DNS_EVENTS = 1; // 0x1
+    field public static final int DETECTION_METHOD_TCP_METRICS = 2; // 0x2
+    field public static final String KEY_DNS_CONSECUTIVE_TIMEOUTS = "dnsConsecutiveTimeouts";
+    field public static final String KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS = "tcpMetricsCollectionPeriodMillis";
+    field public static final String KEY_TCP_PACKET_FAIL_RATE = "tcpPacketFailRate";
+  }
+
+  public class ConnectivityManager {
+    method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
+    method public boolean bindProcessToNetwork(@Nullable android.net.Network);
+    method @NonNull public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network getActiveNetwork();
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo();
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo();
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network[] getAllNetworks();
+    method @Deprecated public boolean getBackgroundDataSetting();
+    method @Nullable public android.net.Network getBoundNetworkForProcess();
+    method public int getConnectionOwnerUid(int, @NonNull java.net.InetSocketAddress, @NonNull java.net.InetSocketAddress);
+    method @Nullable public android.net.ProxyInfo getDefaultProxy();
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.LinkProperties getLinkProperties(@Nullable android.net.Network);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getMultipathPreference(@Nullable android.net.Network);
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkCapabilities getNetworkCapabilities(@Nullable android.net.Network);
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(int);
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(@Nullable android.net.Network);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getNetworkPreference();
+    method @Nullable public byte[] getNetworkWatchlistConfigHash();
+    method @Deprecated @Nullable public static android.net.Network getProcessDefaultNetwork();
+    method public int getRestrictBackgroundStatus();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public boolean isActiveNetworkMetered();
+    method public boolean isDefaultNetworkActive();
+    method @Deprecated public static boolean isNetworkTypeValid(int);
+    method public void registerBestMatchingNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent);
+    method public void releaseNetworkRequest(@NonNull android.app.PendingIntent);
+    method public void removeDefaultNetworkActiveListener(@NonNull android.net.ConnectivityManager.OnNetworkActiveListener);
+    method @Deprecated public void reportBadNetwork(@Nullable android.net.Network);
+    method public void reportNetworkConnectivity(@Nullable android.net.Network, boolean);
+    method public boolean requestBandwidthUpdate(@NonNull android.net.Network);
+    method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback);
+    method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+    method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int);
+    method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler, int);
+    method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent);
+    method @Deprecated public void setNetworkPreference(int);
+    method @Deprecated public static boolean setProcessDefaultNetwork(@Nullable android.net.Network);
+    method public void unregisterNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback);
+    method public void unregisterNetworkCallback(@NonNull android.app.PendingIntent);
+    field @Deprecated public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
+    field public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL";
+    field public static final String ACTION_RESTRICT_BACKGROUND_CHANGED = "android.net.conn.RESTRICT_BACKGROUND_CHANGED";
+    field @Deprecated public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
+    field @Deprecated public static final int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1
+    field public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL";
+    field public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL";
+    field @Deprecated public static final String EXTRA_EXTRA_INFO = "extraInfo";
+    field @Deprecated public static final String EXTRA_IS_FAILOVER = "isFailover";
+    field public static final String EXTRA_NETWORK = "android.net.extra.NETWORK";
+    field @Deprecated public static final String EXTRA_NETWORK_INFO = "networkInfo";
+    field public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST";
+    field @Deprecated public static final String EXTRA_NETWORK_TYPE = "networkType";
+    field public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity";
+    field @Deprecated public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork";
+    field public static final String EXTRA_REASON = "reason";
+    field public static final int MULTIPATH_PREFERENCE_HANDOVER = 1; // 0x1
+    field public static final int MULTIPATH_PREFERENCE_PERFORMANCE = 4; // 0x4
+    field public static final int MULTIPATH_PREFERENCE_RELIABILITY = 2; // 0x2
+    field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1
+    field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3
+    field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2
+    field @Deprecated public static final int TYPE_BLUETOOTH = 7; // 0x7
+    field @Deprecated public static final int TYPE_DUMMY = 8; // 0x8
+    field @Deprecated public static final int TYPE_ETHERNET = 9; // 0x9
+    field @Deprecated public static final int TYPE_MOBILE = 0; // 0x0
+    field @Deprecated public static final int TYPE_MOBILE_DUN = 4; // 0x4
+    field @Deprecated public static final int TYPE_MOBILE_HIPRI = 5; // 0x5
+    field @Deprecated public static final int TYPE_MOBILE_MMS = 2; // 0x2
+    field @Deprecated public static final int TYPE_MOBILE_SUPL = 3; // 0x3
+    field @Deprecated public static final int TYPE_VPN = 17; // 0x11
+    field @Deprecated public static final int TYPE_WIFI = 1; // 0x1
+    field @Deprecated public static final int TYPE_WIMAX = 6; // 0x6
+  }
+
+  public static class ConnectivityManager.NetworkCallback {
+    ctor public ConnectivityManager.NetworkCallback();
+    ctor public ConnectivityManager.NetworkCallback(int);
+    method public void onAvailable(@NonNull android.net.Network);
+    method public void onBlockedStatusChanged(@NonNull android.net.Network, boolean);
+    method public void onCapabilitiesChanged(@NonNull android.net.Network, @NonNull android.net.NetworkCapabilities);
+    method public void onLinkPropertiesChanged(@NonNull android.net.Network, @NonNull android.net.LinkProperties);
+    method public void onLosing(@NonNull android.net.Network, int);
+    method public void onLost(@NonNull android.net.Network);
+    method public void onUnavailable();
+    field public static final int FLAG_INCLUDE_LOCATION_INFO = 1; // 0x1
+  }
+
+  public static interface ConnectivityManager.OnNetworkActiveListener {
+    method public void onNetworkActive();
+  }
+
+  public class DhcpInfo implements android.os.Parcelable {
+    ctor public DhcpInfo();
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.DhcpInfo> CREATOR;
+    field public int dns1;
+    field public int dns2;
+    field public int gateway;
+    field public int ipAddress;
+    field public int leaseDuration;
+    field public int netmask;
+    field public int serverAddress;
+  }
+
+  public final class DnsResolver {
+    method @NonNull public static android.net.DnsResolver getInstance();
+    method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>);
+    method public void query(@Nullable android.net.Network, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>);
+    method public void rawQuery(@Nullable android.net.Network, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>);
+    method public void rawQuery(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>);
+    field public static final int CLASS_IN = 1; // 0x1
+    field public static final int ERROR_PARSE = 0; // 0x0
+    field public static final int ERROR_SYSTEM = 1; // 0x1
+    field public static final int FLAG_EMPTY = 0; // 0x0
+    field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4
+    field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2
+    field public static final int FLAG_NO_RETRY = 1; // 0x1
+    field public static final int TYPE_A = 1; // 0x1
+    field public static final int TYPE_AAAA = 28; // 0x1c
+  }
+
+  public static interface DnsResolver.Callback<T> {
+    method public void onAnswer(@NonNull T, int);
+    method public void onError(@NonNull android.net.DnsResolver.DnsException);
+  }
+
+  public static class DnsResolver.DnsException extends java.lang.Exception {
+    ctor public DnsResolver.DnsException(int, @Nullable Throwable);
+    field public final int code;
+  }
+
+  public class InetAddresses {
+    method public static boolean isNumericAddress(@NonNull String);
+    method @NonNull public static java.net.InetAddress parseNumericAddress(@NonNull String);
+  }
+
+  public final class IpConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.net.ProxyInfo getHttpProxy();
+    method @Nullable public android.net.StaticIpConfiguration getStaticIpConfiguration();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.IpConfiguration> CREATOR;
+  }
+
+  public static final class IpConfiguration.Builder {
+    ctor public IpConfiguration.Builder();
+    method @NonNull public android.net.IpConfiguration build();
+    method @NonNull public android.net.IpConfiguration.Builder setHttpProxy(@Nullable android.net.ProxyInfo);
+    method @NonNull public android.net.IpConfiguration.Builder setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
+  }
+
+  public final class IpPrefix implements android.os.Parcelable {
+    ctor public IpPrefix(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int);
+    method public boolean contains(@NonNull java.net.InetAddress);
+    method public int describeContents();
+    method @NonNull public java.net.InetAddress getAddress();
+    method @IntRange(from=0, to=128) public int getPrefixLength();
+    method @NonNull public byte[] getRawAddress();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR;
+  }
+
+  public class LinkAddress implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.net.InetAddress getAddress();
+    method public int getFlags();
+    method @IntRange(from=0, to=128) public int getPrefixLength();
+    method public int getScope();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkAddress> CREATOR;
+  }
+
+  public final class LinkProperties implements android.os.Parcelable {
+    ctor public LinkProperties();
+    method public boolean addRoute(@NonNull android.net.RouteInfo);
+    method public void clear();
+    method public int describeContents();
+    method @Nullable public java.net.Inet4Address getDhcpServerAddress();
+    method @NonNull public java.util.List<java.net.InetAddress> getDnsServers();
+    method @Nullable public String getDomains();
+    method @Nullable public android.net.ProxyInfo getHttpProxy();
+    method @Nullable public String getInterfaceName();
+    method @NonNull public java.util.List<android.net.LinkAddress> getLinkAddresses();
+    method public int getMtu();
+    method @Nullable public android.net.IpPrefix getNat64Prefix();
+    method @Nullable public String getPrivateDnsServerName();
+    method @NonNull public java.util.List<android.net.RouteInfo> getRoutes();
+    method public boolean isPrivateDnsActive();
+    method public boolean isWakeOnLanSupported();
+    method public void setDhcpServerAddress(@Nullable java.net.Inet4Address);
+    method public void setDnsServers(@NonNull java.util.Collection<java.net.InetAddress>);
+    method public void setDomains(@Nullable String);
+    method public void setHttpProxy(@Nullable android.net.ProxyInfo);
+    method public void setInterfaceName(@Nullable String);
+    method public void setLinkAddresses(@NonNull java.util.Collection<android.net.LinkAddress>);
+    method public void setMtu(int);
+    method public void setNat64Prefix(@Nullable android.net.IpPrefix);
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkProperties> CREATOR;
+  }
+
+  public final class MacAddress implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public static android.net.MacAddress fromBytes(@NonNull byte[]);
+    method @NonNull public static android.net.MacAddress fromString(@NonNull String);
+    method public int getAddressType();
+    method @Nullable public java.net.Inet6Address getLinkLocalIpv6FromEui48Mac();
+    method public boolean isLocallyAssigned();
+    method public boolean matches(@NonNull android.net.MacAddress, @NonNull android.net.MacAddress);
+    method @NonNull public byte[] toByteArray();
+    method @NonNull public String toOuiString();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.net.MacAddress BROADCAST_ADDRESS;
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.MacAddress> CREATOR;
+    field public static final int TYPE_BROADCAST = 3; // 0x3
+    field public static final int TYPE_MULTICAST = 2; // 0x2
+    field public static final int TYPE_UNICAST = 1; // 0x1
+  }
+
+  public class Network implements android.os.Parcelable {
+    method public void bindSocket(java.net.DatagramSocket) throws java.io.IOException;
+    method public void bindSocket(java.net.Socket) throws java.io.IOException;
+    method public void bindSocket(java.io.FileDescriptor) throws java.io.IOException;
+    method public int describeContents();
+    method public static android.net.Network fromNetworkHandle(long);
+    method public java.net.InetAddress[] getAllByName(String) throws java.net.UnknownHostException;
+    method public java.net.InetAddress getByName(String) throws java.net.UnknownHostException;
+    method public long getNetworkHandle();
+    method public javax.net.SocketFactory getSocketFactory();
+    method public java.net.URLConnection openConnection(java.net.URL) throws java.io.IOException;
+    method public java.net.URLConnection openConnection(java.net.URL, java.net.Proxy) throws java.io.IOException;
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.Network> CREATOR;
+  }
+
+  public final class NetworkCapabilities implements android.os.Parcelable {
+    ctor public NetworkCapabilities();
+    ctor public NetworkCapabilities(android.net.NetworkCapabilities);
+    method public int describeContents();
+    method @NonNull public int[] getCapabilities();
+    method @NonNull public int[] getEnterpriseIds();
+    method public int getLinkDownstreamBandwidthKbps();
+    method public int getLinkUpstreamBandwidthKbps();
+    method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
+    method public int getOwnerUid();
+    method public int getSignalStrength();
+    method @Nullable public android.net.TransportInfo getTransportInfo();
+    method public boolean hasCapability(int);
+    method public boolean hasEnterpriseId(int);
+    method public boolean hasTransport(int);
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkCapabilities> CREATOR;
+    field public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17; // 0x11
+    field public static final int NET_CAPABILITY_CBS = 5; // 0x5
+    field public static final int NET_CAPABILITY_DUN = 2; // 0x2
+    field public static final int NET_CAPABILITY_EIMS = 10; // 0xa
+    field public static final int NET_CAPABILITY_ENTERPRISE = 29; // 0x1d
+    field public static final int NET_CAPABILITY_FOREGROUND = 19; // 0x13
+    field public static final int NET_CAPABILITY_FOTA = 3; // 0x3
+    field public static final int NET_CAPABILITY_HEAD_UNIT = 32; // 0x20
+    field public static final int NET_CAPABILITY_IA = 7; // 0x7
+    field public static final int NET_CAPABILITY_IMS = 4; // 0x4
+    field public static final int NET_CAPABILITY_INTERNET = 12; // 0xc
+    field public static final int NET_CAPABILITY_MCX = 23; // 0x17
+    field public static final int NET_CAPABILITY_MMS = 0; // 0x0
+    field public static final int NET_CAPABILITY_MMTEL = 33; // 0x21
+    field public static final int NET_CAPABILITY_NOT_CONGESTED = 20; // 0x14
+    field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb
+    field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd
+    field public static final int NET_CAPABILITY_NOT_ROAMING = 18; // 0x12
+    field public static final int NET_CAPABILITY_NOT_SUSPENDED = 21; // 0x15
+    field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf
+    field public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35; // 0x23
+    field public static final int NET_CAPABILITY_PRIORITIZE_LATENCY = 34; // 0x22
+    field public static final int NET_CAPABILITY_RCS = 8; // 0x8
+    field public static final int NET_CAPABILITY_SUPL = 1; // 0x1
+    field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19
+    field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe
+    field public static final int NET_CAPABILITY_VALIDATED = 16; // 0x10
+    field public static final int NET_CAPABILITY_WIFI_P2P = 6; // 0x6
+    field public static final int NET_CAPABILITY_XCAP = 9; // 0x9
+    field public static final int NET_ENTERPRISE_ID_1 = 1; // 0x1
+    field public static final int NET_ENTERPRISE_ID_2 = 2; // 0x2
+    field public static final int NET_ENTERPRISE_ID_3 = 3; // 0x3
+    field public static final int NET_ENTERPRISE_ID_4 = 4; // 0x4
+    field public static final int NET_ENTERPRISE_ID_5 = 5; // 0x5
+    field public static final int SIGNAL_STRENGTH_UNSPECIFIED = -2147483648; // 0x80000000
+    field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2
+    field public static final int TRANSPORT_CELLULAR = 0; // 0x0
+    field public static final int TRANSPORT_ETHERNET = 3; // 0x3
+    field public static final int TRANSPORT_LOWPAN = 6; // 0x6
+    field public static final int TRANSPORT_THREAD = 9; // 0x9
+    field public static final int TRANSPORT_USB = 8; // 0x8
+    field public static final int TRANSPORT_VPN = 4; // 0x4
+    field public static final int TRANSPORT_WIFI = 1; // 0x1
+    field public static final int TRANSPORT_WIFI_AWARE = 5; // 0x5
+  }
+
+  @Deprecated public class NetworkInfo implements android.os.Parcelable {
+    ctor @Deprecated public NetworkInfo(int, int, @Nullable String, @Nullable String);
+    method @Deprecated public int describeContents();
+    method @Deprecated @NonNull public android.net.NetworkInfo.DetailedState getDetailedState();
+    method @Deprecated public String getExtraInfo();
+    method @Deprecated public String getReason();
+    method @Deprecated public android.net.NetworkInfo.State getState();
+    method @Deprecated public int getSubtype();
+    method @Deprecated public String getSubtypeName();
+    method @Deprecated public int getType();
+    method @Deprecated public String getTypeName();
+    method @Deprecated public boolean isAvailable();
+    method @Deprecated public boolean isConnected();
+    method @Deprecated public boolean isConnectedOrConnecting();
+    method @Deprecated public boolean isFailover();
+    method @Deprecated public boolean isRoaming();
+    method @Deprecated public void setDetailedState(@NonNull android.net.NetworkInfo.DetailedState, @Nullable String, @Nullable String);
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
+    field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkInfo> CREATOR;
+  }
+
+  @Deprecated public enum NetworkInfo.DetailedState {
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState AUTHENTICATING;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState BLOCKED;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CAPTIVE_PORTAL_CHECK;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTED;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTING;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTED;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTING;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState FAILED;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState IDLE;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState OBTAINING_IPADDR;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SCANNING;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SUSPENDED;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState VERIFYING_POOR_LINK;
+  }
+
+  @Deprecated public enum NetworkInfo.State {
+    enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTED;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTING;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTED;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTING;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.State SUSPENDED;
+    enum_constant @Deprecated public static final android.net.NetworkInfo.State UNKNOWN;
+  }
+
+  public class NetworkRequest implements android.os.Parcelable {
+    method public boolean canBeSatisfiedBy(@Nullable android.net.NetworkCapabilities);
+    method public int describeContents();
+    method @NonNull public int[] getCapabilities();
+    method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
+    method @NonNull public int[] getTransportTypes();
+    method public boolean hasCapability(int);
+    method public boolean hasTransport(int);
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkRequest> CREATOR;
+  }
+
+  public static class NetworkRequest.Builder {
+    ctor public NetworkRequest.Builder();
+    ctor public NetworkRequest.Builder(@NonNull android.net.NetworkRequest);
+    method public android.net.NetworkRequest.Builder addCapability(int);
+    method public android.net.NetworkRequest.Builder addTransportType(int);
+    method public android.net.NetworkRequest build();
+    method @NonNull public android.net.NetworkRequest.Builder clearCapabilities();
+    method public android.net.NetworkRequest.Builder removeCapability(int);
+    method public android.net.NetworkRequest.Builder removeTransportType(int);
+    method @NonNull public android.net.NetworkRequest.Builder setIncludeOtherUidNetworks(boolean);
+    method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
+    method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier);
+  }
+
+  public class ParseException extends java.lang.RuntimeException {
+    ctor public ParseException(@NonNull String);
+    ctor public ParseException(@NonNull String, @NonNull Throwable);
+    field public String response;
+  }
+
+  public class ProxyInfo implements android.os.Parcelable {
+    ctor public ProxyInfo(@Nullable android.net.ProxyInfo);
+    method public static android.net.ProxyInfo buildDirectProxy(String, int);
+    method public static android.net.ProxyInfo buildDirectProxy(String, int, java.util.List<java.lang.String>);
+    method public static android.net.ProxyInfo buildPacProxy(android.net.Uri);
+    method @NonNull public static android.net.ProxyInfo buildPacProxy(@NonNull android.net.Uri, int);
+    method public int describeContents();
+    method public String[] getExclusionList();
+    method public String getHost();
+    method public android.net.Uri getPacFileUrl();
+    method public int getPort();
+    method public boolean isValid();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.ProxyInfo> CREATOR;
+  }
+
+  public final class RouteInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.net.IpPrefix getDestination();
+    method @Nullable public java.net.InetAddress getGateway();
+    method @Nullable public String getInterface();
+    method public int getType();
+    method public boolean hasGateway();
+    method public boolean isDefaultRoute();
+    method public boolean matches(java.net.InetAddress);
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.RouteInfo> CREATOR;
+    field public static final int RTN_THROW = 9; // 0x9
+    field public static final int RTN_UNICAST = 1; // 0x1
+    field public static final int RTN_UNREACHABLE = 7; // 0x7
+  }
+
+  public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+    method public final void close();
+    method public final void start(@IntRange(from=0xa, to=0xe10) int);
+    method public final void stop();
+    field public static final int ERROR_HARDWARE_ERROR = -31; // 0xffffffe1
+    field public static final int ERROR_INSUFFICIENT_RESOURCES = -32; // 0xffffffe0
+    field public static final int ERROR_INVALID_INTERVAL = -24; // 0xffffffe8
+    field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb
+    field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9
+    field public static final int ERROR_INVALID_NETWORK = -20; // 0xffffffec
+    field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea
+    field public static final int ERROR_INVALID_SOCKET = -25; // 0xffffffe7
+    field public static final int ERROR_SOCKET_NOT_IDLE = -26; // 0xffffffe6
+    field public static final int ERROR_UNSUPPORTED = -30; // 0xffffffe2
+  }
+
+  public static class SocketKeepalive.Callback {
+    ctor public SocketKeepalive.Callback();
+    method public void onDataReceived();
+    method public void onError(int);
+    method public void onStarted();
+    method public void onStopped();
+  }
+
+  public final class StaticIpConfiguration implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<java.net.InetAddress> getDnsServers();
+    method @Nullable public String getDomains();
+    method @Nullable public java.net.InetAddress getGateway();
+    method @NonNull public android.net.LinkAddress getIpAddress();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR;
+  }
+
+  public static final class StaticIpConfiguration.Builder {
+    ctor public StaticIpConfiguration.Builder();
+    method @NonNull public android.net.StaticIpConfiguration build();
+    method @NonNull public android.net.StaticIpConfiguration.Builder setDnsServers(@NonNull Iterable<java.net.InetAddress>);
+    method @NonNull public android.net.StaticIpConfiguration.Builder setDomains(@Nullable String);
+    method @NonNull public android.net.StaticIpConfiguration.Builder setGateway(@Nullable java.net.InetAddress);
+    method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@NonNull android.net.LinkAddress);
+  }
+
+  public interface TransportInfo {
+  }
+
+}
+
diff --git a/framework/cronet_disabled/api/lint-baseline.txt b/framework/cronet_disabled/api/lint-baseline.txt
new file mode 100644
index 0000000..2f4004a
--- /dev/null
+++ b/framework/cronet_disabled/api/lint-baseline.txt
@@ -0,0 +1,4 @@
+// Baseline format: 1.0
+VisiblySynchronized: android.net.NetworkInfo#toString():
+    Internal locks must not be exposed (synchronizing on this or class is still
+    externally observable): method android.net.NetworkInfo.toString()
diff --git a/framework/cronet_disabled/api/module-lib-current.txt b/framework/cronet_disabled/api/module-lib-current.txt
new file mode 100644
index 0000000..193bd92
--- /dev/null
+++ b/framework/cronet_disabled/api/module-lib-current.txt
@@ -0,0 +1,239 @@
+// Signature format: 2.0
+package android.net {
+
+  public final class ConnectivityFrameworkInitializer {
+    method public static void registerServiceWrappers();
+  }
+
+  public class ConnectivityManager {
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void addUidToMeteredNetworkAllowList(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void addUidToMeteredNetworkDenyList(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void factoryReset();
+    method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List<android.net.NetworkStateSnapshot> getAllNetworkStateSnapshots();
+    method @Nullable public android.net.ProxyInfo getGlobalProxy();
+    method @NonNull public static android.util.Range<java.lang.Integer> getIpSecNetIdRange();
+    method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.LinkProperties getRedactedLinkPropertiesForPackage(@NonNull android.net.LinkProperties, int, @NonNull String);
+    method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.NetworkCapabilities getRedactedNetworkCapabilitiesForPackage(@NonNull android.net.NetworkCapabilities, int, @NonNull String);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackForUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void removeUidFromMeteredNetworkAllowList(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void removeUidFromMeteredNetworkDenyList(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void replaceFirewallChain(int, @NonNull int[]);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
+    method @Deprecated public boolean requestRouteToHostAddress(int, java.net.InetAddress);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptPartialConnectivity(@NonNull android.net.Network, boolean, boolean);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptUnvalidated(@NonNull android.net.Network, boolean, boolean);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAvoidUnvalidated(@NonNull android.net.Network);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setFirewallChainEnabled(int, boolean);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setLegacyLockdownVpnEnabled(boolean);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreferences(@NonNull android.os.UserHandle, @NonNull java.util.List<android.net.ProfileNetworkPreference>, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setRequireVpnForUids(boolean, @NonNull java.util.Collection<android.util.Range<java.lang.Integer>>);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setUidFirewallRule(int, int, int);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setVpnDefaultForUids(@NonNull String, @NonNull java.util.Collection<android.util.Range<java.lang.Integer>>);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void startCaptivePortalApp(@NonNull android.net.Network);
+    method public void systemReady();
+    field public static final String ACTION_CLEAR_DNS_CACHE = "android.net.action.CLEAR_DNS_CACHE";
+    field public static final String ACTION_PROMPT_LOST_VALIDATION = "android.net.action.PROMPT_LOST_VALIDATION";
+    field public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY = "android.net.action.PROMPT_PARTIAL_CONNECTIVITY";
+    field public static final String ACTION_PROMPT_UNVALIDATED = "android.net.action.PROMPT_UNVALIDATED";
+    field public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 262144; // 0x40000
+    field public static final int BLOCKED_METERED_REASON_DATA_SAVER = 65536; // 0x10000
+    field public static final int BLOCKED_METERED_REASON_MASK = -65536; // 0xffff0000
+    field public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 131072; // 0x20000
+    field public static final int BLOCKED_REASON_APP_STANDBY = 4; // 0x4
+    field public static final int BLOCKED_REASON_BATTERY_SAVER = 1; // 0x1
+    field public static final int BLOCKED_REASON_DOZE = 2; // 0x2
+    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 public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8
+    field public static final int FIREWALL_CHAIN_DOZABLE = 1; // 0x1
+    field public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5; // 0x5
+    field public static final int FIREWALL_CHAIN_OEM_DENY_1 = 7; // 0x7
+    field public static final int FIREWALL_CHAIN_OEM_DENY_2 = 8; // 0x8
+    field public static final int FIREWALL_CHAIN_OEM_DENY_3 = 9; // 0x9
+    field public static final int FIREWALL_CHAIN_POWERSAVE = 3; // 0x3
+    field public static final int FIREWALL_CHAIN_RESTRICTED = 4; // 0x4
+    field public static final int FIREWALL_CHAIN_STANDBY = 2; // 0x2
+    field public static final int FIREWALL_RULE_ALLOW = 1; // 0x1
+    field public static final int FIREWALL_RULE_DEFAULT = 0; // 0x0
+    field public static final int FIREWALL_RULE_DENY = 2; // 0x2
+    field public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; // 0x0
+    field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1
+    field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING = 3; // 0x3
+    field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK = 2; // 0x2
+  }
+
+  public static class ConnectivityManager.NetworkCallback {
+    method public void onBlockedStatusChanged(@NonNull android.net.Network, int);
+  }
+
+  public class ConnectivitySettingsManager {
+    method public static void clearGlobalProxy(@NonNull android.content.Context);
+    method @Nullable public static String getCaptivePortalHttpUrl(@NonNull android.content.Context);
+    method public static int getCaptivePortalMode(@NonNull android.content.Context, int);
+    method @NonNull public static java.time.Duration getConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+    method @NonNull public static android.util.Range<java.lang.Integer> getDnsResolverSampleRanges(@NonNull android.content.Context);
+    method @NonNull public static java.time.Duration getDnsResolverSampleValidityDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+    method public static int getDnsResolverSuccessThresholdPercent(@NonNull android.content.Context, int);
+    method @Nullable public static android.net.ProxyInfo getGlobalProxy(@NonNull android.content.Context);
+    method public static long getIngressRateLimitInBytesPerSecond(@NonNull android.content.Context);
+    method @NonNull public static java.time.Duration getMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+    method public static boolean getMobileDataAlwaysOn(@NonNull android.content.Context, boolean);
+    method @NonNull public static java.util.Set<java.lang.Integer> getMobileDataPreferredUids(@NonNull android.content.Context);
+    method public static int getNetworkAvoidBadWifi(@NonNull android.content.Context);
+    method @Nullable public static String getNetworkMeteredMultipathPreference(@NonNull android.content.Context);
+    method public static int getNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, int);
+    method @NonNull public static java.time.Duration getNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+    method @NonNull public static String getPrivateDnsDefaultMode(@NonNull android.content.Context);
+    method @Nullable public static String getPrivateDnsHostname(@NonNull android.content.Context);
+    method public static int getPrivateDnsMode(@NonNull android.content.Context);
+    method @NonNull public static java.util.Set<java.lang.Integer> getUidsAllowedOnRestrictedNetworks(@NonNull android.content.Context);
+    method public static boolean getWifiAlwaysRequested(@NonNull android.content.Context, boolean);
+    method @NonNull public static java.time.Duration getWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+    method public static void setCaptivePortalHttpUrl(@NonNull android.content.Context, @Nullable String);
+    method public static void setCaptivePortalMode(@NonNull android.content.Context, int);
+    method public static void setConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+    method public static void setDnsResolverSampleRanges(@NonNull android.content.Context, @NonNull android.util.Range<java.lang.Integer>);
+    method public static void setDnsResolverSampleValidityDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+    method public static void setDnsResolverSuccessThresholdPercent(@NonNull android.content.Context, @IntRange(from=0, to=100) int);
+    method public static void setGlobalProxy(@NonNull android.content.Context, @NonNull android.net.ProxyInfo);
+    method public static void setIngressRateLimitInBytesPerSecond(@NonNull android.content.Context, @IntRange(from=-1L, to=4294967295L) long);
+    method public static void setMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+    method public static void setMobileDataAlwaysOn(@NonNull android.content.Context, boolean);
+    method public static void setMobileDataPreferredUids(@NonNull android.content.Context, @NonNull java.util.Set<java.lang.Integer>);
+    method public static void setNetworkAvoidBadWifi(@NonNull android.content.Context, int);
+    method public static void setNetworkMeteredMultipathPreference(@NonNull android.content.Context, @NonNull String);
+    method public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, @IntRange(from=0) int);
+    method public static void setNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+    method public static void setPrivateDnsDefaultMode(@NonNull android.content.Context, @NonNull int);
+    method public static void setPrivateDnsHostname(@NonNull android.content.Context, @Nullable String);
+    method public static void setPrivateDnsMode(@NonNull android.content.Context, int);
+    method public static void setUidsAllowedOnRestrictedNetworks(@NonNull android.content.Context, @NonNull java.util.Set<java.lang.Integer>);
+    method public static void setWifiAlwaysRequested(@NonNull android.content.Context, boolean);
+    method public static void setWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+    field public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; // 0x2
+    field public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0; // 0x0
+    field public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1; // 0x1
+    field public static final int NETWORK_AVOID_BAD_WIFI_AVOID = 2; // 0x2
+    field public static final int NETWORK_AVOID_BAD_WIFI_IGNORE = 0; // 0x0
+    field public static final int NETWORK_AVOID_BAD_WIFI_PROMPT = 1; // 0x1
+    field public static final int PRIVATE_DNS_MODE_OFF = 1; // 0x1
+    field public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC = 2; // 0x2
+    field public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3; // 0x3
+  }
+
+  public final class DhcpOption implements android.os.Parcelable {
+    ctor public DhcpOption(byte, @Nullable byte[]);
+    method public int describeContents();
+    method public byte getType();
+    method @Nullable public byte[] getValue();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.DhcpOption> CREATOR;
+  }
+
+  public final class NetworkAgentConfig implements android.os.Parcelable {
+    method @Nullable public String getSubscriberId();
+    method public boolean isBypassableVpn();
+    method public boolean isVpnValidationRequired();
+  }
+
+  public static final class NetworkAgentConfig.Builder {
+    method @NonNull public android.net.NetworkAgentConfig.Builder setBypassableVpn(boolean);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setLocalRoutesExcludedForVpn(boolean);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setVpnRequiresValidation(boolean);
+  }
+
+  public final class NetworkCapabilities implements android.os.Parcelable {
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public java.util.Set<java.lang.Integer> getAllowedUids();
+    method @Nullable public java.util.Set<android.util.Range<java.lang.Integer>> getUids();
+    method public boolean hasForbiddenCapability(int);
+    field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL
+    field public static final long REDACT_FOR_ACCESS_FINE_LOCATION = 1L; // 0x1L
+    field public static final long REDACT_FOR_LOCAL_MAC_ADDRESS = 2L; // 0x2L
+    field public static final long REDACT_FOR_NETWORK_SETTINGS = 4L; // 0x4L
+    field public static final long REDACT_NONE = 0L; // 0x0L
+    field public static final int TRANSPORT_TEST = 7; // 0x7
+  }
+
+  public static final class NetworkCapabilities.Builder {
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAllowedUids(@NonNull java.util.Set<java.lang.Integer>);
+    method @NonNull public android.net.NetworkCapabilities.Builder setUids(@Nullable java.util.Set<android.util.Range<java.lang.Integer>>);
+  }
+
+  public class NetworkRequest implements android.os.Parcelable {
+    method @NonNull public int[] getEnterpriseIds();
+    method @NonNull public int[] getForbiddenCapabilities();
+    method public boolean hasEnterpriseId(int);
+    method public boolean hasForbiddenCapability(int);
+  }
+
+  public static class NetworkRequest.Builder {
+    method @NonNull public android.net.NetworkRequest.Builder addForbiddenCapability(int);
+    method @NonNull public android.net.NetworkRequest.Builder removeForbiddenCapability(int);
+    method @NonNull public android.net.NetworkRequest.Builder setUids(@Nullable java.util.Set<android.util.Range<java.lang.Integer>>);
+  }
+
+  public final class ProfileNetworkPreference implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public int[] getExcludedUids();
+    method @NonNull public int[] getIncludedUids();
+    method public int getPreference();
+    method public int getPreferenceEnterpriseId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.ProfileNetworkPreference> CREATOR;
+  }
+
+  public static final class ProfileNetworkPreference.Builder {
+    ctor public ProfileNetworkPreference.Builder();
+    method @NonNull public android.net.ProfileNetworkPreference build();
+    method @NonNull public android.net.ProfileNetworkPreference.Builder setExcludedUids(@NonNull int[]);
+    method @NonNull public android.net.ProfileNetworkPreference.Builder setIncludedUids(@NonNull int[]);
+    method @NonNull public android.net.ProfileNetworkPreference.Builder setPreference(int);
+    method @NonNull public android.net.ProfileNetworkPreference.Builder setPreferenceEnterpriseId(int);
+  }
+
+  public final class TestNetworkInterface implements android.os.Parcelable {
+    ctor public TestNetworkInterface(@NonNull android.os.ParcelFileDescriptor, @NonNull String);
+    method public int describeContents();
+    method @NonNull public android.os.ParcelFileDescriptor getFileDescriptor();
+    method @NonNull public String getInterfaceName();
+    method @Nullable public android.net.MacAddress getMacAddress();
+    method public int getMtu();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.TestNetworkInterface> CREATOR;
+  }
+
+  public class TestNetworkManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TEST_NETWORKS) public android.net.TestNetworkInterface createTapInterface();
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TEST_NETWORKS) public android.net.TestNetworkInterface createTunInterface(@NonNull java.util.Collection<android.net.LinkAddress>);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_TEST_NETWORKS) public void setupTestNetwork(@NonNull String, @NonNull android.os.IBinder);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_TEST_NETWORKS) public void teardownTestNetwork(@NonNull android.net.Network);
+    field public static final String TEST_TAP_PREFIX = "testtap";
+  }
+
+  public final class TestNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+    ctor public TestNetworkSpecifier(@NonNull String);
+    method public int describeContents();
+    method @Nullable public String getInterfaceName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.TestNetworkSpecifier> CREATOR;
+  }
+
+  public interface TransportInfo {
+    method public default long getApplicableRedactions();
+    method @NonNull public default android.net.TransportInfo makeCopy(long);
+  }
+
+  public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
+    ctor @Deprecated public VpnTransportInfo(int, @Nullable String);
+    method @Nullable public String getSessionId();
+    method @NonNull public android.net.VpnTransportInfo makeCopy(long);
+  }
+
+}
+
diff --git a/Cronet/api/module-lib-removed.txt b/framework/cronet_disabled/api/module-lib-removed.txt
similarity index 100%
rename from Cronet/api/module-lib-removed.txt
rename to framework/cronet_disabled/api/module-lib-removed.txt
diff --git a/framework/cronet_disabled/api/removed.txt b/framework/cronet_disabled/api/removed.txt
new file mode 100644
index 0000000..303a1e6
--- /dev/null
+++ b/framework/cronet_disabled/api/removed.txt
@@ -0,0 +1,11 @@
+// Signature format: 2.0
+package android.net {
+
+  public class ConnectivityManager {
+    method @Deprecated public boolean requestRouteToHost(int, int);
+    method @Deprecated public int startUsingNetworkFeature(int, String);
+    method @Deprecated public int stopUsingNetworkFeature(int, String);
+  }
+
+}
+
diff --git a/framework/cronet_disabled/api/system-current.txt b/framework/cronet_disabled/api/system-current.txt
new file mode 100644
index 0000000..4a2ed8a
--- /dev/null
+++ b/framework/cronet_disabled/api/system-current.txt
@@ -0,0 +1,544 @@
+// Signature format: 2.0
+package android.net {
+
+  public class CaptivePortal implements android.os.Parcelable {
+    method @Deprecated public void logEvent(int, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork();
+    method public void useNetwork();
+    field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64
+    field public static final int APP_RETURN_DISMISSED = 0; // 0x0
+    field public static final int APP_RETURN_UNWANTED = 1; // 0x1
+    field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2
+  }
+
+  public final class CaptivePortalData implements android.os.Parcelable {
+    method public int describeContents();
+    method public long getByteLimit();
+    method public long getExpiryTimeMillis();
+    method public long getRefreshTimeMillis();
+    method @Nullable public android.net.Uri getUserPortalUrl();
+    method public int getUserPortalUrlSource();
+    method @Nullable public CharSequence getVenueFriendlyName();
+    method @Nullable public android.net.Uri getVenueInfoUrl();
+    method public int getVenueInfoUrlSource();
+    method public boolean isCaptive();
+    method public boolean isSessionExtendable();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0; // 0x0
+    field public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1; // 0x1
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortalData> CREATOR;
+  }
+
+  public static class CaptivePortalData.Builder {
+    ctor public CaptivePortalData.Builder();
+    ctor public CaptivePortalData.Builder(@Nullable android.net.CaptivePortalData);
+    method @NonNull public android.net.CaptivePortalData build();
+    method @NonNull public android.net.CaptivePortalData.Builder setBytesRemaining(long);
+    method @NonNull public android.net.CaptivePortalData.Builder setCaptive(boolean);
+    method @NonNull public android.net.CaptivePortalData.Builder setExpiryTime(long);
+    method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long);
+    method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean);
+    method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri);
+    method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri, int);
+    method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable CharSequence);
+    method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri);
+    method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri, int);
+  }
+
+  public class ConnectivityManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull android.os.ParcelFileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
+    method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull java.net.Socket, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCaptivePortalServerUrl();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener);
+    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public int registerNetworkProvider(@NonNull android.net.NetworkProvider);
+    method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull java.util.concurrent.Executor, @NonNull android.net.QosCallback);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
+    method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
+    method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi();
+    method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int);
+    method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public void unregisterNetworkProvider(@NonNull android.net.NetworkProvider);
+    method public void unregisterQosCallback(@NonNull android.net.QosCallback);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
+    field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC";
+    field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
+    field public static final int TETHERING_BLUETOOTH = 2; // 0x2
+    field public static final int TETHERING_USB = 1; // 0x1
+    field public static final int TETHERING_WIFI = 0; // 0x0
+    field @Deprecated public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd
+    field @Deprecated public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0
+    field @Deprecated public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb
+    field public static final int TYPE_NONE = -1; // 0xffffffff
+    field @Deprecated public static final int TYPE_PROXY = 16; // 0x10
+    field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd
+  }
+
+  @Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback {
+    ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback();
+    method @Deprecated public void onTetheringFailed();
+    method @Deprecated public void onTetheringStarted();
+  }
+
+  @Deprecated public static interface ConnectivityManager.OnTetheringEntitlementResultListener {
+    method @Deprecated public void onTetheringEntitlementResult(int);
+  }
+
+  @Deprecated public abstract static class ConnectivityManager.OnTetheringEventCallback {
+    ctor @Deprecated public ConnectivityManager.OnTetheringEventCallback();
+    method @Deprecated public void onUpstreamChanged(@Nullable android.net.Network);
+  }
+
+  public final class DscpPolicy implements android.os.Parcelable {
+    method @Nullable public java.net.InetAddress getDestinationAddress();
+    method @Nullable public android.util.Range<java.lang.Integer> getDestinationPortRange();
+    method public int getDscpValue();
+    method public int getPolicyId();
+    method public int getProtocol();
+    method @Nullable public java.net.InetAddress getSourceAddress();
+    method public int getSourcePort();
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.DscpPolicy> CREATOR;
+    field public static final int PROTOCOL_ANY = -1; // 0xffffffff
+    field public static final int SOURCE_PORT_ANY = -1; // 0xffffffff
+  }
+
+  public static final class DscpPolicy.Builder {
+    ctor public DscpPolicy.Builder(int, int);
+    method @NonNull public android.net.DscpPolicy build();
+    method @NonNull public android.net.DscpPolicy.Builder setDestinationAddress(@NonNull java.net.InetAddress);
+    method @NonNull public android.net.DscpPolicy.Builder setDestinationPortRange(@NonNull android.util.Range<java.lang.Integer>);
+    method @NonNull public android.net.DscpPolicy.Builder setProtocol(int);
+    method @NonNull public android.net.DscpPolicy.Builder setSourceAddress(@NonNull java.net.InetAddress);
+    method @NonNull public android.net.DscpPolicy.Builder setSourcePort(int);
+  }
+
+  public final class InvalidPacketException extends java.lang.Exception {
+    ctor public InvalidPacketException(int);
+    method public int getError();
+    field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb
+    field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9
+    field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea
+  }
+
+  public final class IpConfiguration implements android.os.Parcelable {
+    ctor public IpConfiguration();
+    ctor public IpConfiguration(@NonNull android.net.IpConfiguration);
+    method @NonNull public android.net.IpConfiguration.IpAssignment getIpAssignment();
+    method @NonNull public android.net.IpConfiguration.ProxySettings getProxySettings();
+    method public void setHttpProxy(@Nullable android.net.ProxyInfo);
+    method public void setIpAssignment(@NonNull android.net.IpConfiguration.IpAssignment);
+    method public void setProxySettings(@NonNull android.net.IpConfiguration.ProxySettings);
+    method public void setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
+  }
+
+  public enum IpConfiguration.IpAssignment {
+    enum_constant public static final android.net.IpConfiguration.IpAssignment DHCP;
+    enum_constant public static final android.net.IpConfiguration.IpAssignment STATIC;
+    enum_constant public static final android.net.IpConfiguration.IpAssignment UNASSIGNED;
+  }
+
+  public enum IpConfiguration.ProxySettings {
+    enum_constant public static final android.net.IpConfiguration.ProxySettings NONE;
+    enum_constant public static final android.net.IpConfiguration.ProxySettings PAC;
+    enum_constant public static final android.net.IpConfiguration.ProxySettings STATIC;
+    enum_constant public static final android.net.IpConfiguration.ProxySettings UNASSIGNED;
+  }
+
+  public final class IpPrefix implements android.os.Parcelable {
+    ctor public IpPrefix(@NonNull String);
+  }
+
+  public class KeepalivePacketData {
+    ctor protected KeepalivePacketData(@NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull byte[]) throws android.net.InvalidPacketException;
+    method @NonNull public java.net.InetAddress getDstAddress();
+    method public int getDstPort();
+    method @NonNull public byte[] getPacket();
+    method @NonNull public java.net.InetAddress getSrcAddress();
+    method public int getSrcPort();
+  }
+
+  public class LinkAddress implements android.os.Parcelable {
+    ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int);
+    ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int, long, long);
+    ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int);
+    ctor public LinkAddress(@NonNull String);
+    ctor public LinkAddress(@NonNull String, int, int);
+    method public long getDeprecationTime();
+    method public long getExpirationTime();
+    method public boolean isGlobalPreferred();
+    method public boolean isIpv4();
+    method public boolean isIpv6();
+    method public boolean isSameAddressAs(@Nullable android.net.LinkAddress);
+    field public static final long LIFETIME_PERMANENT = 9223372036854775807L; // 0x7fffffffffffffffL
+    field public static final long LIFETIME_UNKNOWN = -1L; // 0xffffffffffffffffL
+  }
+
+  public final class LinkProperties implements android.os.Parcelable {
+    ctor public LinkProperties(@Nullable android.net.LinkProperties);
+    ctor public LinkProperties(@Nullable android.net.LinkProperties, boolean);
+    method public boolean addDnsServer(@NonNull java.net.InetAddress);
+    method public boolean addLinkAddress(@NonNull android.net.LinkAddress);
+    method public boolean addPcscfServer(@NonNull java.net.InetAddress);
+    method @NonNull public java.util.List<java.net.InetAddress> getAddresses();
+    method @NonNull public java.util.List<java.lang.String> getAllInterfaceNames();
+    method @NonNull public java.util.List<android.net.LinkAddress> getAllLinkAddresses();
+    method @NonNull public java.util.List<android.net.RouteInfo> getAllRoutes();
+    method @Nullable public android.net.Uri getCaptivePortalApiUrl();
+    method @Nullable public android.net.CaptivePortalData getCaptivePortalData();
+    method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers();
+    method @Nullable public String getTcpBufferSizes();
+    method @NonNull public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers();
+    method public boolean hasGlobalIpv6Address();
+    method public boolean hasIpv4Address();
+    method public boolean hasIpv4DefaultRoute();
+    method public boolean hasIpv4DnsServer();
+    method public boolean hasIpv6DefaultRoute();
+    method public boolean hasIpv6DnsServer();
+    method public boolean isIpv4Provisioned();
+    method public boolean isIpv6Provisioned();
+    method public boolean isProvisioned();
+    method public boolean isReachable(@NonNull java.net.InetAddress);
+    method public boolean removeDnsServer(@NonNull java.net.InetAddress);
+    method public boolean removeLinkAddress(@NonNull android.net.LinkAddress);
+    method public boolean removeRoute(@NonNull android.net.RouteInfo);
+    method public void setCaptivePortalApiUrl(@Nullable android.net.Uri);
+    method public void setCaptivePortalData(@Nullable android.net.CaptivePortalData);
+    method public void setPcscfServers(@NonNull java.util.Collection<java.net.InetAddress>);
+    method public void setPrivateDnsServerName(@Nullable String);
+    method public void setTcpBufferSizes(@Nullable String);
+    method public void setUsePrivateDns(boolean);
+    method public void setValidatedPrivateDnsServers(@NonNull java.util.Collection<java.net.InetAddress>);
+  }
+
+  public final class NattKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable {
+    ctor public NattKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[]) throws android.net.InvalidPacketException;
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NattKeepalivePacketData> CREATOR;
+  }
+
+  public class Network implements android.os.Parcelable {
+    ctor public Network(@NonNull android.net.Network);
+    method public int getNetId();
+    method @NonNull public android.net.Network getPrivateDnsBypassingCopy();
+  }
+
+  public abstract class NetworkAgent {
+    ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
+    ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkScore, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider);
+    method @Nullable public android.net.Network getNetwork();
+    method public void markConnected();
+    method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
+    method public void onAutomaticReconnectDisabled();
+    method public void onBandwidthUpdateRequested();
+    method public void onDscpPolicyStatusUpdated(int, int);
+    method public void onNetworkCreated();
+    method public void onNetworkDestroyed();
+    method public void onNetworkUnwanted();
+    method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter);
+    method public void onQosCallbackUnregistered(int);
+    method public void onRemoveKeepalivePacketFilter(int);
+    method public void onSaveAcceptUnvalidated(boolean);
+    method public void onSignalStrengthThresholdsUpdated(@NonNull int[]);
+    method public void onStartSocketKeepalive(int, @NonNull java.time.Duration, @NonNull android.net.KeepalivePacketData);
+    method public void onStopSocketKeepalive(int);
+    method public void onValidationStatus(int, @Nullable android.net.Uri);
+    method @NonNull public android.net.Network register();
+    method public void sendAddDscpPolicy(@NonNull android.net.DscpPolicy);
+    method public void sendLinkProperties(@NonNull android.net.LinkProperties);
+    method public void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities);
+    method public void sendNetworkScore(@NonNull android.net.NetworkScore);
+    method public void sendNetworkScore(@IntRange(from=0, to=99) int);
+    method public final void sendQosCallbackError(int, int);
+    method public final void sendQosSessionAvailable(int, int, @NonNull android.net.QosSessionAttributes);
+    method public final void sendQosSessionLost(int, int, int);
+    method public void sendRemoveAllDscpPolicies();
+    method public void sendRemoveDscpPolicy(int);
+    method public final void sendSocketKeepaliveEvent(int, int);
+    method @Deprecated public void setLegacySubtype(int, @NonNull String);
+    method public void setLingerDuration(@NonNull java.time.Duration);
+    method public void setTeardownDelayMillis(@IntRange(from=0, to=0x1388) int);
+    method public void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
+    method public void unregister();
+    method public void unregisterAfterReplacement(@IntRange(from=0, to=0x1388) int);
+    field public static final int DSCP_POLICY_STATUS_DELETED = 4; // 0x4
+    field public static final int DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES = 3; // 0x3
+    field public static final int DSCP_POLICY_STATUS_POLICY_NOT_FOUND = 5; // 0x5
+    field public static final int DSCP_POLICY_STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED = 2; // 0x2
+    field public static final int DSCP_POLICY_STATUS_REQUEST_DECLINED = 1; // 0x1
+    field public static final int DSCP_POLICY_STATUS_SUCCESS = 0; // 0x0
+    field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2
+    field public static final int VALIDATION_STATUS_VALID = 1; // 0x1
+  }
+
+  public final class NetworkAgentConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getLegacyType();
+    method @NonNull public String getLegacyTypeName();
+    method public boolean isExplicitlySelected();
+    method public boolean isPartialConnectivityAcceptable();
+    method public boolean isUnvalidatedConnectivityAcceptable();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkAgentConfig> CREATOR;
+  }
+
+  public static final class NetworkAgentConfig.Builder {
+    ctor public NetworkAgentConfig.Builder();
+    method @NonNull public android.net.NetworkAgentConfig build();
+    method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyExtraInfo(@NonNull String);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubType(int);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubTypeName(@NonNull String);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setNat64DetectionEnabled(boolean);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setProvisioningNotificationEnabled(boolean);
+    method @NonNull public android.net.NetworkAgentConfig.Builder setUnvalidatedConnectivityAcceptable(boolean);
+  }
+
+  public final class NetworkCapabilities implements android.os.Parcelable {
+    method @NonNull public int[] getAdministratorUids();
+    method @Nullable public static String getCapabilityCarrierName(int);
+    method @Nullable public String getSsid();
+    method @NonNull public java.util.Set<java.lang.Integer> getSubscriptionIds();
+    method @NonNull public int[] getTransportTypes();
+    method @Nullable public java.util.List<android.net.Network> getUnderlyingNetworks();
+    method public boolean isPrivateDnsBroken();
+    method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
+    field public static final int NET_CAPABILITY_BIP = 31; // 0x1f
+    field public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; // 0x1c
+    field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
+    field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a
+    field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18
+    field public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; // 0x1b
+    field public static final int NET_CAPABILITY_VSIM = 30; // 0x1e
+  }
+
+  public static final class NetworkCapabilities.Builder {
+    ctor public NetworkCapabilities.Builder();
+    ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities);
+    method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int);
+    method @NonNull public android.net.NetworkCapabilities.Builder addEnterpriseId(int);
+    method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int);
+    method @NonNull public android.net.NetworkCapabilities build();
+    method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int);
+    method @NonNull public android.net.NetworkCapabilities.Builder removeEnterpriseId(int);
+    method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]);
+    method @NonNull public android.net.NetworkCapabilities.Builder setLinkDownstreamBandwidthKbps(int);
+    method @NonNull public android.net.NetworkCapabilities.Builder setLinkUpstreamBandwidthKbps(int);
+    method @NonNull public android.net.NetworkCapabilities.Builder setNetworkSpecifier(@Nullable android.net.NetworkSpecifier);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setOwnerUid(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorPackageName(@Nullable String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorUid(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkCapabilities.Builder setSignalStrength(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String);
+    method @NonNull public android.net.NetworkCapabilities.Builder setSubscriptionIds(@NonNull java.util.Set<java.lang.Integer>);
+    method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo);
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
+    method @NonNull public static android.net.NetworkCapabilities.Builder withoutDefaultCapabilities();
+  }
+
+  public class NetworkProvider {
+    ctor public NetworkProvider(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void declareNetworkRequestUnfulfillable(@NonNull android.net.NetworkRequest);
+    method public int getProviderId();
+    method public void onNetworkRequestWithdrawn(@NonNull android.net.NetworkRequest);
+    method public void onNetworkRequested(@NonNull android.net.NetworkRequest, @IntRange(from=0, to=99) int, int);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void registerNetworkOffer(@NonNull android.net.NetworkScore, @NonNull android.net.NetworkCapabilities, @NonNull java.util.concurrent.Executor, @NonNull android.net.NetworkProvider.NetworkOfferCallback);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void unregisterNetworkOffer(@NonNull android.net.NetworkProvider.NetworkOfferCallback);
+    field public static final int ID_NONE = -1; // 0xffffffff
+  }
+
+  public static interface NetworkProvider.NetworkOfferCallback {
+    method public void onNetworkNeeded(@NonNull android.net.NetworkRequest);
+    method public void onNetworkUnneeded(@NonNull android.net.NetworkRequest);
+  }
+
+  public class NetworkReleasedException extends java.lang.Exception {
+    ctor public NetworkReleasedException();
+  }
+
+  public class NetworkRequest implements android.os.Parcelable {
+    method @Nullable public String getRequestorPackageName();
+    method public int getRequestorUid();
+  }
+
+  public static class NetworkRequest.Builder {
+    method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int);
+    method @NonNull public android.net.NetworkRequest.Builder setSubscriptionIds(@NonNull java.util.Set<java.lang.Integer>);
+  }
+
+  public final class NetworkScore implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getKeepConnectedReason();
+    method public int getLegacyInt();
+    method public boolean isExiting();
+    method public boolean isTransportPrimary();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkScore> CREATOR;
+    field public static final int KEEP_CONNECTED_FOR_HANDOVER = 1; // 0x1
+    field public static final int KEEP_CONNECTED_NONE = 0; // 0x0
+  }
+
+  public static final class NetworkScore.Builder {
+    ctor public NetworkScore.Builder();
+    method @NonNull public android.net.NetworkScore build();
+    method @NonNull public android.net.NetworkScore.Builder setExiting(boolean);
+    method @NonNull public android.net.NetworkScore.Builder setKeepConnectedReason(int);
+    method @NonNull public android.net.NetworkScore.Builder setLegacyInt(int);
+    method @NonNull public android.net.NetworkScore.Builder setTransportPrimary(boolean);
+  }
+
+  public final class OemNetworkPreferences implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getNetworkPreferences();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.OemNetworkPreferences> CREATOR;
+    field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID = 1; // 0x1
+    field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK = 2; // 0x2
+    field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY = 3; // 0x3
+    field public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4; // 0x4
+    field public static final int OEM_NETWORK_PREFERENCE_UNINITIALIZED = 0; // 0x0
+  }
+
+  public static final class OemNetworkPreferences.Builder {
+    ctor public OemNetworkPreferences.Builder();
+    ctor public OemNetworkPreferences.Builder(@NonNull android.net.OemNetworkPreferences);
+    method @NonNull public android.net.OemNetworkPreferences.Builder addNetworkPreference(@NonNull String, int);
+    method @NonNull public android.net.OemNetworkPreferences build();
+    method @NonNull public android.net.OemNetworkPreferences.Builder clearNetworkPreference(@NonNull String);
+  }
+
+  public abstract class QosCallback {
+    ctor public QosCallback();
+    method public void onError(@NonNull android.net.QosCallbackException);
+    method public void onQosSessionAvailable(@NonNull android.net.QosSession, @NonNull android.net.QosSessionAttributes);
+    method public void onQosSessionLost(@NonNull android.net.QosSession);
+  }
+
+  public static class QosCallback.QosCallbackRegistrationException extends java.lang.RuntimeException {
+  }
+
+  public final class QosCallbackException extends java.lang.Exception {
+    ctor public QosCallbackException(@NonNull String);
+    ctor public QosCallbackException(@NonNull Throwable);
+  }
+
+  public abstract class QosFilter {
+    method @NonNull public abstract android.net.Network getNetwork();
+    method public abstract boolean matchesLocalAddress(@NonNull java.net.InetAddress, int, int);
+    method public boolean matchesProtocol(int);
+    method public abstract boolean matchesRemoteAddress(@NonNull java.net.InetAddress, int, int);
+  }
+
+  public final class QosSession implements android.os.Parcelable {
+    ctor public QosSession(int, int);
+    method public int describeContents();
+    method public int getSessionId();
+    method public int getSessionType();
+    method public long getUniqueId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSession> CREATOR;
+    field public static final int TYPE_EPS_BEARER = 1; // 0x1
+    field public static final int TYPE_NR_BEARER = 2; // 0x2
+  }
+
+  public interface QosSessionAttributes {
+  }
+
+  public final class QosSocketInfo implements android.os.Parcelable {
+    ctor public QosSocketInfo(@NonNull android.net.Network, @NonNull java.net.Socket) throws java.io.IOException;
+    ctor public QosSocketInfo(@NonNull android.net.Network, @NonNull java.net.DatagramSocket) throws java.io.IOException;
+    method public int describeContents();
+    method @NonNull public java.net.InetSocketAddress getLocalSocketAddress();
+    method @NonNull public android.net.Network getNetwork();
+    method @Nullable public java.net.InetSocketAddress getRemoteSocketAddress();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSocketInfo> CREATOR;
+  }
+
+  public final class RouteInfo implements android.os.Parcelable {
+    ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int);
+    ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int, int);
+    method public int getMtu();
+  }
+
+  public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+    method public final void start(@IntRange(from=0xa, to=0xe10) int, int, @Nullable android.net.Network);
+    field public static final int ERROR_NO_SUCH_SLOT = -33; // 0xffffffdf
+    field public static final int FLAG_AUTOMATIC_ON_OFF = 1; // 0x1
+    field public static final int SUCCESS = 0; // 0x0
+  }
+
+  public class SocketLocalAddressChangedException extends java.lang.Exception {
+    ctor public SocketLocalAddressChangedException();
+  }
+
+  public class SocketNotBoundException extends java.lang.Exception {
+    ctor public SocketNotBoundException();
+  }
+
+  public class SocketNotConnectedException extends java.lang.Exception {
+    ctor public SocketNotConnectedException();
+  }
+
+  public class SocketRemoteAddressChangedException extends java.lang.Exception {
+    ctor public SocketRemoteAddressChangedException();
+  }
+
+  public final class StaticIpConfiguration implements android.os.Parcelable {
+    ctor public StaticIpConfiguration();
+    ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
+    method public void addDnsServer(@NonNull java.net.InetAddress);
+    method public void clear();
+    method @NonNull public java.util.List<android.net.RouteInfo> getRoutes(@Nullable String);
+  }
+
+  public final class TcpKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable {
+    ctor public TcpKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[], int, int, int, int, int, int) throws android.net.InvalidPacketException;
+    method public int describeContents();
+    method public int getIpTos();
+    method public int getIpTtl();
+    method public int getTcpAck();
+    method public int getTcpSeq();
+    method public int getTcpWindow();
+    method public int getTcpWindowScale();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.TcpKeepalivePacketData> CREATOR;
+  }
+
+  public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo {
+    ctor public VpnTransportInfo(int, @Nullable String, boolean, boolean);
+    method public boolean areLongLivedTcpConnectionsExpensive();
+    method public int describeContents();
+    method public int getType();
+    method public boolean isBypassable();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.VpnTransportInfo> CREATOR;
+  }
+
+}
+
+package android.net.apf {
+
+  public final class ApfCapabilities implements android.os.Parcelable {
+    ctor public ApfCapabilities(int, int, int);
+    method public int describeContents();
+    method public static boolean getApfDrop8023Frames();
+    method @NonNull public static int[] getApfEtherTypeBlackList();
+    method public boolean hasDataAccess();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.apf.ApfCapabilities> CREATOR;
+    field public final int apfPacketFormat;
+    field public final int apfVersionSupported;
+    field public final int maximumApfProgramSize;
+  }
+
+}
+
diff --git a/framework/cronet_disabled/api/system-lint-baseline.txt b/framework/cronet_disabled/api/system-lint-baseline.txt
new file mode 100644
index 0000000..9a97707
--- /dev/null
+++ b/framework/cronet_disabled/api/system-lint-baseline.txt
@@ -0,0 +1 @@
+// Baseline format: 1.0
diff --git a/Cronet/api/system-removed.txt b/framework/cronet_disabled/api/system-removed.txt
similarity index 100%
rename from Cronet/api/system-removed.txt
rename to framework/cronet_disabled/api/system-removed.txt
diff --git a/framework/jarjar-excludes.txt b/framework/jarjar-excludes.txt
index 1311765..9b48d57 100644
--- a/framework/jarjar-excludes.txt
+++ b/framework/jarjar-excludes.txt
@@ -23,3 +23,8 @@
 
 # TODO (b/217115866): add jarjar rules for Nearby
 android\.nearby\..+
+
+# Don't touch anything that's already under android.net.http (cronet)
+# This is required since android.net.http contains api classes and hidden classes.
+# TODO: Remove this after hidden classes are moved to different package
+android\.net\.http\..+
\ No newline at end of file
diff --git a/framework/jni/android_net_NetworkUtils.cpp b/framework/jni/android_net_NetworkUtils.cpp
index 38e0059..ca297e5 100644
--- a/framework/jni/android_net_NetworkUtils.cpp
+++ b/framework/jni/android_net_NetworkUtils.cpp
@@ -23,6 +23,7 @@
 #include <netinet/in.h>
 #include <string.h>
 
+#include <bpf/BpfClassic.h>
 #include <DnsProxydProtocol.h> // NETID_USE_LOCAL_NAMESERVERS
 #include <nativehelper/JNIPlatformHelp.h>
 #include <utils/Log.h>
@@ -55,11 +56,10 @@
 
 static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jclass clazz, jobject javaFd)
 {
-    struct sock_filter filter_code[] = {
-        // Reject all.
-        BPF_STMT(BPF_RET | BPF_K, 0)
+    static struct sock_filter filter_code[] = {
+        BPF_REJECT,
     };
-    struct sock_fprog filter = {
+    static const struct sock_fprog filter = {
         sizeof(filter_code) / sizeof(filter_code[0]),
         filter_code,
     };
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 39c5af2..2315521 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -1232,16 +1232,19 @@
     }
 
     /**
-     * Preference for {@link ProfileNetworkPreference#setPreference(int)}.
+     * Preference for {@link ProfileNetworkPreference.Builder#setPreference(int)}.
      * See {@link #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)}
-     * Specify that the traffic for this user should by follow the default rules.
+     * Specify that the traffic for this user should by follow the default rules:
+     * applications in the profile designated by the UserHandle behave like any
+     * other application and use the system default network as their default
+     * network. Compare other PROFILE_NETWORK_PREFERENCE_* settings.
      * @hide
      */
     @SystemApi(client = MODULE_LIBRARIES)
     public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0;
 
     /**
-     * Preference for {@link ProfileNetworkPreference#setPreference(int)}.
+     * Preference for {@link ProfileNetworkPreference.Builder#setPreference(int)}.
      * See {@link #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)}
      * Specify that the traffic for this user should by default go on a network with
      * {@link NetworkCapabilities#NET_CAPABILITY_ENTERPRISE}, and on the system default network
@@ -1252,16 +1255,38 @@
     public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1;
 
     /**
-     * Preference for {@link ProfileNetworkPreference#setPreference(int)}.
+     * Preference for {@link ProfileNetworkPreference.Builder#setPreference(int)}.
      * See {@link #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)}
      * Specify that the traffic for this user should by default go on a network with
      * {@link NetworkCapabilities#NET_CAPABILITY_ENTERPRISE} and if no such network is available
-     * should not go on the system default network
+     * should not have a default network at all (that is, network accesses that
+     * do not specify a network explicitly terminate with an error), even if there
+     * is a system default network available to apps outside this preference.
+     * The apps can still use a non-enterprise network if they request it explicitly
+     * provided that specific network doesn't require any specific permission they
+     * do not hold.
      * @hide
      */
     @SystemApi(client = MODULE_LIBRARIES)
     public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK = 2;
 
+    /**
+     * Preference for {@link ProfileNetworkPreference.Builder#setPreference(int)}.
+     * See {@link #setProfileNetworkPreferences(UserHandle, List, Executor, Runnable)}
+     * Specify that the traffic for this user should by default go on a network with
+     * {@link NetworkCapabilities#NET_CAPABILITY_ENTERPRISE}.
+     * If there is no such network, the apps will have no default
+     * network at all, even if there are available non-enterprise networks on the
+     * device (that is, network accesses that do not specify a network explicitly
+     * terminate with an error). Additionally, the designated apps should be
+     * blocked from using any non-enterprise network even if they specify it
+     * explicitly, unless they hold specific privilege overriding this (see
+     * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS}).
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING = 3;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {
@@ -1378,6 +1403,17 @@
         }
     }
 
+    private static UidRange[] getUidRangeArray(@NonNull Collection<Range<Integer>> ranges) {
+        Objects.requireNonNull(ranges);
+        final UidRange[] rangesArray = new UidRange[ranges.size()];
+        int index = 0;
+        for (Range<Integer> range : ranges) {
+            rangesArray[index++] = new UidRange(range.getLower(), range.getUpper());
+        }
+
+        return rangesArray;
+    }
+
     /**
      * Adds or removes a requirement for given UID ranges to use the VPN.
      *
@@ -1397,6 +1433,12 @@
      * {@link NetworkCallback#onBlockedStatusChanged} callbacks called after the changes take
      * effect.
      * <p>
+     * This method will block the specified UIDs from accessing non-VPN networks, but does not
+     * affect what the UIDs get as their default network.
+     * Compare {@link #setVpnDefaultForUids(String, Collection)}, which declares that the UIDs
+     * should only have a VPN as their default network, but does not block them from accessing other
+     * networks if they request them explicitly with the {@link Network} API.
+     * <p>
      * This method should be called only by the VPN code.
      *
      * @param ranges the UID ranges to restrict
@@ -1416,11 +1458,7 @@
         // This method is not necessarily expected to be used outside the system server, so
         // parceling may not be necessary, but it could be used out-of-process, e.g., by the network
         // stack process, or by tests.
-        UidRange[] rangesArray = new UidRange[ranges.size()];
-        int index = 0;
-        for (Range<Integer> range : ranges) {
-            rangesArray[index++] = new UidRange(range.getLower(), range.getUpper());
-        }
+        final UidRange[] rangesArray = getUidRangeArray(ranges);
         try {
             mService.setRequireVpnForUids(requireVpn, rangesArray);
         } catch (RemoteException e) {
@@ -1429,6 +1467,57 @@
     }
 
     /**
+     * Inform the system that this VPN session should manage the passed UIDs.
+     *
+     * A VPN with the specified session ID may call this method to inform the system that the UIDs
+     * in the specified range are subject to a VPN.
+     * When this is called, the system will only choose a VPN for the default network of the UIDs in
+     * the specified ranges.
+     *
+     * This method declares that the UIDs in the range will only have a VPN for their default
+     * network, but does not block the UIDs from accessing other networks (permissions allowing) by
+     * explicitly requesting it with the {@link Network} API.
+     * Compare {@link #setRequireVpnForUids(boolean, Collection)}, which does not affect what
+     * network the UIDs get as default, but will block them from accessing non-VPN networks.
+     *
+     * @param session The VPN session which manages the passed UIDs.
+     * @param ranges The uid ranges which will treat VPN as their only default network.
+     *
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_STACK,
+            android.Manifest.permission.NETWORK_SETTINGS})
+    @SystemApi(client = MODULE_LIBRARIES)
+    public void setVpnDefaultForUids(@NonNull String session,
+            @NonNull Collection<Range<Integer>> ranges) {
+        Objects.requireNonNull(ranges);
+        final UidRange[] rangesArray = getUidRangeArray(ranges);
+        try {
+            mService.setVpnNetworkPreference(session, rangesArray);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Temporarily set automaticOnOff keeplaive TCP polling alarm timer to 1 second.
+     *
+     * TODO: Remove this when the TCP polling design is replaced with callback.
+     * @param timeMs The time of expiry, with System.currentTimeMillis() base. The value should be
+     *               set no more than 5 minutes in the future.
+     * @hide
+     */
+    public void setTestLowTcpPollingTimerForKeepalive(long timeMs) {
+        try {
+            mService.setTestLowTcpPollingTimerForKeepalive(timeMs);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Informs ConnectivityService of whether the legacy lockdown VPN, as implemented by
      * LockdownVpnTracker, is in use. This is deprecated for new devices starting from Android 12
      * but is still supported for backwards compatibility.
@@ -2140,9 +2229,13 @@
         /** The requested keepalive was successfully started. */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public void onStarted() {}
+        /** The keepalive was resumed after being paused by the system. */
+        public void onResumed() {}
         /** The keepalive was successfully stopped. */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public void onStopped() {}
+        /** The keepalive was paused automatically by the system. */
+        public void onPaused() {}
         /** An error occurred. */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public void onError(int error) {}
@@ -2206,16 +2299,12 @@
         private final ISocketKeepaliveCallback mCallback;
         private final ExecutorService mExecutor;
 
-        private volatile Integer mSlot;
-
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         public void stop() {
             try {
                 mExecutor.execute(() -> {
                     try {
-                        if (mSlot != null) {
-                            mService.stopKeepalive(mNetwork, mSlot);
-                        }
+                        mService.stopKeepalive(mCallback);
                     } catch (RemoteException e) {
                         Log.e(TAG, "Error stopping packet keepalive: ", e);
                         throw e.rethrowFromSystemServer();
@@ -2233,11 +2322,10 @@
             mExecutor = Executors.newSingleThreadExecutor();
             mCallback = new ISocketKeepaliveCallback.Stub() {
                 @Override
-                public void onStarted(int slot) {
+                public void onStarted() {
                     final long token = Binder.clearCallingIdentity();
                     try {
                         mExecutor.execute(() -> {
-                            mSlot = slot;
                             callback.onStarted();
                         });
                     } finally {
@@ -2246,11 +2334,22 @@
                 }
 
                 @Override
+                public void onResumed() {
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        mExecutor.execute(() -> {
+                            callback.onResumed();
+                        });
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+
+                @Override
                 public void onStopped() {
                     final long token = Binder.clearCallingIdentity();
                     try {
                         mExecutor.execute(() -> {
-                            mSlot = null;
                             callback.onStopped();
                         });
                     } finally {
@@ -2260,11 +2359,23 @@
                 }
 
                 @Override
+                public void onPaused() {
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        mExecutor.execute(() -> {
+                            callback.onPaused();
+                        });
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                    mExecutor.shutdown();
+                }
+
+                @Override
                 public void onError(int error) {
                     final long token = Binder.clearCallingIdentity();
                     try {
                         mExecutor.execute(() -> {
-                            mSlot = null;
                             callback.onError(error);
                         });
                     } finally {
@@ -2410,7 +2521,7 @@
     @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD)
     public @NonNull SocketKeepalive createSocketKeepalive(@NonNull Network network,
             @NonNull Socket socket,
-            @NonNull Executor executor,
+            @NonNull @CallbackExecutor Executor executor,
             @NonNull Callback callback) {
         ParcelFileDescriptor dup;
         try {
@@ -2424,6 +2535,26 @@
     }
 
     /**
+     * Get the supported keepalive count for each transport configured in resource overlays.
+     *
+     * @return An array of supported keepalive count for each transport type.
+     * @hide
+     */
+    @RequiresPermission(anyOf = { android.Manifest.permission.NETWORK_SETTINGS,
+            // CTS 13 used QUERY_ALL_PACKAGES to get the resource value, which was implemented
+            // as below in KeepaliveUtils. Also allow that permission so that KeepaliveUtils can
+            // use this method and avoid breaking released CTS. Apps that have this permission
+            // can query the resource themselves anyway.
+            android.Manifest.permission.QUERY_ALL_PACKAGES })
+    public int[] getSupportedKeepalives() {
+        try {
+            return mService.getSupportedKeepalives();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Ensure that a network route exists to deliver traffic to the specified
      * host via the specified network interface. An attempt to add a route that
      * already exists is ignored, but treated as successful.
@@ -4790,6 +4921,7 @@
     @RequiresPermission(anyOf = {
             NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
             android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD,
             android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS})
     public void registerSystemDefaultNetworkCallback(@NonNull NetworkCallback networkCallback,
             @NonNull Handler handler) {
@@ -5382,9 +5514,9 @@
      * @return {@code uid} if the connection is found and the app has permission to observe it
      *     (e.g., if it is associated with the calling VPN app's VpnService tunnel) or {@link
      *     android.os.Process#INVALID_UID} if the connection is not found.
-     * @throws {@link SecurityException} if the caller is not the active VpnService for the current
+     * @throws SecurityException if the caller is not the active VpnService for the current
      *     user.
-     * @throws {@link IllegalArgumentException} if an unsupported protocol is requested.
+     * @throws IllegalArgumentException if an unsupported protocol is requested.
      */
     public int getConnectionOwnerUid(
             int protocol, @NonNull InetSocketAddress local, @NonNull InetSocketAddress remote) {
@@ -5924,6 +6056,30 @@
     }
 
     /**
+     * Get firewall rule of specified firewall chain on specified uid.
+     *
+     * @param chain target chain.
+     * @param uid   target uid
+     * @return either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
+     * @throws UnsupportedOperationException if called on pre-T devices.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_STACK,
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+    })
+    public int getUidFirewallRule(@FirewallChain final int chain, final int uid) {
+        try {
+            return mService.getUidFirewallRule(chain, uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Enables or disables the specified firewall chain.
      *
      * @param chain target chain.
@@ -5992,4 +6148,13 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /** @hide */
+    public IBinder getCompanionDeviceManagerProxyService() {
+        try {
+            return mService.getCompanionDeviceManagerProxyService();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java
index 822e67d..67dacb8 100644
--- a/framework/src/android/net/ConnectivitySettingsManager.java
+++ b/framework/src/android/net/ConnectivitySettingsManager.java
@@ -28,6 +28,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.net.ConnectivityManager.MultipathPreference;
 import android.os.Binder;
 import android.os.Build;
@@ -36,6 +37,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArraySet;
+import android.util.Log;
 import android.util.Range;
 
 import com.android.net.module.util.ConnectivitySettingsUtils;
@@ -55,6 +57,7 @@
  */
 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public class ConnectivitySettingsManager {
+    private static final String TAG = ConnectivitySettingsManager.class.getSimpleName();
 
     private ConnectivitySettingsManager() {}
 
@@ -696,10 +699,20 @@
     /**
      * Set global http proxy settings from given {@link ProxyInfo}.
      *
+     * <p class="note">
+     * While a {@link ProxyInfo} for a PAC proxy can be specified, not all devices support
+     * PAC proxies. In particular, smaller devices like watches often do not have the capabilities
+     * necessary to interpret the PAC file. In such cases, calling this API with a PAC proxy
+     * results in undefined behavior, including possibly breaking networking for applications.
+     * You can test for this by checking for the presence of {@link PackageManager.FEATURE_WEBVIEW}.
+     * </p>
+     *
      * @param context The {@link Context} to set the setting.
      * @param proxyInfo The {@link ProxyInfo} for global http proxy settings which build from
      *                    {@link ProxyInfo#buildPacProxy(Uri)} or
      *                    {@link ProxyInfo#buildDirectProxy(String, int, List)}
+     * @throws UnsupportedOperationException if |proxyInfo| codes for a PAC proxy but the system
+     *                                       does not support PAC proxies.
      */
     public static void setGlobalProxy(@NonNull Context context, @NonNull ProxyInfo proxyInfo) {
         final String host = proxyInfo.getHost();
@@ -707,6 +720,14 @@
         final String exclusionList = proxyInfo.getExclusionListAsString();
         final String pacFileUrl = proxyInfo.getPacFileUrl().toString();
 
+
+        if (!TextUtils.isEmpty(pacFileUrl)) {
+            final PackageManager pm = context.getPackageManager();
+            if (null != pm && !pm.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
+                Log.wtf(TAG, "PAC proxy can't be installed on a device without FEATURE_WEBVIEW");
+            }
+        }
+
         if (TextUtils.isEmpty(pacFileUrl)) {
             Settings.Global.putString(context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, host);
             Settings.Global.putInt(context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, port);
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index 29fea00..ebe8bca 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -188,12 +188,14 @@
 
     void startNattKeepaliveWithFd(in Network network, in ParcelFileDescriptor pfd, int resourceId,
             int intervalSeconds, in ISocketKeepaliveCallback cb, String srcAddr,
-            String dstAddr);
+            String dstAddr, boolean automaticOnOffKeepalives, in Network underpinnedNetwork);
 
     void startTcpKeepalive(in Network network, in ParcelFileDescriptor pfd, int intervalSeconds,
             in ISocketKeepaliveCallback cb);
 
-    void stopKeepalive(in Network network, int slot);
+    void stopKeepalive(in ISocketKeepaliveCallback cb);
+
+    int[] getSupportedKeepalives();
 
     String getCaptivePortalServerUrl();
 
@@ -242,9 +244,17 @@
 
     void setUidFirewallRule(int chain, int uid, int rule);
 
+    int getUidFirewallRule(int chain, int uid);
+
     void setFirewallChainEnabled(int chain, boolean enable);
 
     boolean getFirewallChainEnabled(int chain);
 
     void replaceFirewallChain(int chain, in int[] uids);
+
+    IBinder getCompanionDeviceManagerProxyService();
+
+    void setVpnNetworkPreference(String session, in UidRange[] ranges);
+
+    void setTestLowTcpPollingTimerForKeepalive(long timeMs);
 }
diff --git a/framework/src/android/net/ISocketKeepaliveCallback.aidl b/framework/src/android/net/ISocketKeepaliveCallback.aidl
index 020fbca..0a1de6c 100644
--- a/framework/src/android/net/ISocketKeepaliveCallback.aidl
+++ b/framework/src/android/net/ISocketKeepaliveCallback.aidl
@@ -24,11 +24,15 @@
 oneway interface ISocketKeepaliveCallback
 {
     /** The keepalive was successfully started. */
-    void onStarted(int slot);
+    void onStarted();
     /** The keepalive was successfully stopped. */
     void onStopped();
     /** The keepalive was stopped because of an error. */
     void onError(int error);
     /** The keepalive on a TCP socket was stopped because the socket received data. */
     void onDataReceived();
+    /** The keepalive was paused by the system because it's not necessary right now. */
+    void onPaused();
+    /** The keepalive was resumed by the system after being suspended. */
+    void onResumed();
 }
diff --git a/framework/src/android/net/LinkAddress.java b/framework/src/android/net/LinkAddress.java
index d48b8c7..90f55b3 100644
--- a/framework/src/android/net/LinkAddress.java
+++ b/framework/src/android/net/LinkAddress.java
@@ -487,17 +487,23 @@
      */
     @SystemApi
     public boolean isGlobalPreferred() {
-        /**
-         * Note that addresses flagged as IFA_F_OPTIMISTIC are
-         * simultaneously flagged as IFA_F_TENTATIVE (when the tentative
-         * state has cleared either DAD has succeeded or failed, and both
-         * flags are cleared regardless).
-         */
-        int flags = getFlags();
         return (scope == RT_SCOPE_UNIVERSE
                 && !isIpv6ULA()
-                && (flags & (IFA_F_DADFAILED | IFA_F_DEPRECATED)) == 0L
-                && ((flags & IFA_F_TENTATIVE) == 0L || (flags & IFA_F_OPTIMISTIC) != 0L));
+                && isPreferred());
+    }
+
+    /**
+     * Checks if the address is a preferred address.
+     *
+     * @hide
+     */
+    public boolean isPreferred() {
+        //  Note that addresses flagged as IFA_F_OPTIMISTIC are simultaneously flagged as
+        //  IFA_F_TENTATIVE (when the tentative state has cleared either DAD has succeeded or
+        //  failed, and both flags are cleared regardless).
+        int flags = getFlags();
+        return (flags & (IFA_F_DADFAILED | IFA_F_DEPRECATED)) == 0L
+                && ((flags & IFA_F_TENTATIVE) == 0L || (flags & IFA_F_OPTIMISTIC) != 0L);
     }
 
     /**
diff --git a/framework/src/android/net/LinkProperties.java b/framework/src/android/net/LinkProperties.java
index b7ee846..9fb9bc6 100644
--- a/framework/src/android/net/LinkProperties.java
+++ b/framework/src/android/net/LinkProperties.java
@@ -16,19 +16,18 @@
 
 package android.net;
 
+import static android.net.connectivity.ConnectivityCompatChanges.EXCLUDED_ROUTES;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.app.compat.CompatChanges;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.LinkPropertiesUtils;
@@ -58,17 +57,6 @@
  *
  */
 public final class LinkProperties implements Parcelable {
-    /**
-     * The {@link #getRoutes()} now can contain excluded as well as included routes. Use
-     * {@link RouteInfo#getType()} to determine route type.
-     *
-     * @hide
-     */
-    @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2)
-    @VisibleForTesting
-    public static final long EXCLUDED_ROUTES = 186082280;
-
     // The interface described by the network link.
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private String mIfaceName;
@@ -1468,6 +1456,10 @@
      * @hide
      */
     public boolean isIdenticalPcscfs(@NonNull LinkProperties target) {
+        // Per 3GPP TS 24.229, B.2.2.1 PDP context activation and P-CSCF discovery
+        // list order is important, so on U+ compare one by one
+        if (SdkLevel.isAtLeastU()) return target.getPcscfServers().equals(mPcscfs);
+        // but for safety old behaviour on pre-U:
         Collection<InetAddress> targetPcscfs = target.getPcscfServers();
         return (mPcscfs.size() == targetPcscfs.size()) ?
                     mPcscfs.containsAll(targetPcscfs) : false;
diff --git a/framework/src/android/net/NattSocketKeepalive.java b/framework/src/android/net/NattSocketKeepalive.java
index 56cc923..a83b5b4 100644
--- a/framework/src/android/net/NattSocketKeepalive.java
+++ b/framework/src/android/net/NattSocketKeepalive.java
@@ -47,13 +47,42 @@
         mResourceId = resourceId;
     }
 
+    /**
+     * Request that keepalive be started with the given {@code intervalSec}.
+     *
+     * When a VPN is running with the network for this keepalive as its underlying network, the
+     * system can monitor the TCP connections on that VPN to determine whether this keepalive is
+     * necessary. To enable this behavior, pass {@link SocketKeepalive#FLAG_AUTOMATIC_ON_OFF} into
+     * the flags. When this is enabled, the system will disable sending keepalive packets when
+     * there are no TCP connections over the VPN(s) running over this network to save battery, and
+     * restart sending them as soon as any TCP connection is opened over one of the VPN networks.
+     * When no VPN is running on top of this network, this flag has no effect, i.e. the keepalives
+     * are always sent with the specified interval.
+     *
+     * Also {@see SocketKeepalive}.
+     *
+     * @param intervalSec The target interval in seconds between keepalive packet transmissions.
+     *                    The interval should be between 10 seconds and 3600 seconds. Otherwise,
+     *                    the supplied {@link Callback} will see a call to
+     *                    {@link Callback#onError(int)} with {@link #ERROR_INVALID_INTERVAL}.
+     * @param flags Flags to enable/disable available options on this keepalive.
+     * @param underpinnedNetwork The underpinned network of this keepalive.
+     *
+     * @hide
+     */
     @Override
-    protected void startImpl(int intervalSec) {
+    protected void startImpl(int intervalSec, int flags, Network underpinnedNetwork) {
+        if (0 != (flags & ~FLAG_AUTOMATIC_ON_OFF)) {
+            throw new IllegalArgumentException("Illegal flag value for "
+                    + this.getClass().getSimpleName() + " : " + flags);
+        }
+        final boolean automaticOnOffKeepalives = 0 != (flags & FLAG_AUTOMATIC_ON_OFF);
         mExecutor.execute(() -> {
             try {
                 mService.startNattKeepaliveWithFd(mNetwork, mPfd, mResourceId,
-                        intervalSec, mCallback,
-                        mSource.getHostAddress(), mDestination.getHostAddress());
+                        intervalSec, mCallback, mSource.getHostAddress(),
+                        mDestination.getHostAddress(), automaticOnOffKeepalives,
+                        underpinnedNetwork);
             } catch (RemoteException e) {
                 Log.e(TAG, "Error starting socket keepalive: ", e);
                 throw e.rethrowFromSystemServer();
@@ -65,9 +94,7 @@
     protected void stopImpl() {
         mExecutor.execute(() -> {
             try {
-                if (mSlot != null) {
-                    mService.stopKeepalive(mNetwork, mSlot);
-                }
+                mService.stopKeepalive(mCallback);
             } catch (RemoteException e) {
                 Log.e(TAG, "Error stopping socket keepalive: ", e);
                 throw e.rethrowFromSystemServer();
diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java
index 1486619..177f7e3 100644
--- a/framework/src/android/net/NetworkAgent.java
+++ b/framework/src/android/net/NetworkAgent.java
@@ -283,7 +283,6 @@
      *   arg2 = interval in seconds
      *   obj = KeepalivePacketData object describing the data to be sent
      *
-     * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
      * @hide
      */
     public static final int CMD_START_SOCKET_KEEPALIVE = BASE + 11;
@@ -291,7 +290,9 @@
     /**
      * Requests that the specified keepalive packet be stopped.
      *
-     * arg1 = hardware slot number of the keepalive to stop.
+     * arg1 = unused
+     * arg2 = error code (SUCCESS)
+     * obj = callback to identify the keepalive
      *
      * Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
      * @hide
@@ -434,6 +435,14 @@
     public static final int CMD_DSCP_POLICY_STATUS = BASE + 28;
 
     /**
+     * Sent by the NetworkAgent to ConnectivityService to notify that this network is expected to be
+     * replaced within the specified time by a similar network.
+     * arg1 = timeout in milliseconds
+     * @hide
+     */
+    public static final int EVENT_UNREGISTER_AFTER_REPLACEMENT = BASE + 29;
+
+    /**
      * DSCP policy was successfully added.
      */
     public static final int DSCP_POLICY_STATUS_SUCCESS = 0;
@@ -475,14 +484,6 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface DscpPolicyStatus {}
 
-    /**
-     * Sent by the NetworkAgent to ConnectivityService to notify that this network is expected to be
-     * replaced within the specified time by a similar network.
-     * arg1 = timeout in milliseconds
-     * @hide
-     */
-    public static final int EVENT_UNREGISTER_AFTER_REPLACEMENT = BASE + 29;
-
     private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
         final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType,
                 config.legacyTypeName, config.legacySubTypeName);
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index e07601f..92e9599 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
 import static com.android.net.module.util.BitUtils.appendStringRepresentationOfBitMaskToStringBuilder;
+import static com.android.net.module.util.BitUtils.describeDifferences;
 
 import android.annotation.IntDef;
 import android.annotation.LongDef;
@@ -52,18 +53,70 @@
 import java.util.StringJoiner;
 
 /**
- * Representation of the capabilities of an active network. Instances are
- * typically obtained through
- * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)}
- * or {@link ConnectivityManager#getNetworkCapabilities(Network)}.
- * <p>
- * This replaces the old {@link ConnectivityManager#TYPE_MOBILE} method of
- * network selection. Rather than indicate a need for Wi-Fi because an
- * application needs high bandwidth and risk obsolescence when a new, fast
- * network appears (like LTE), the application should specify it needs high
- * bandwidth. Similarly if an application needs an unmetered network for a bulk
- * transfer it can specify that rather than assuming all cellular based
- * connections are metered and all Wi-Fi based connections are not.
+ * Representation of the capabilities of an active network.
+ *
+ * <p>@see <a href="https://developer.android.com/training/basics/network-ops/reading-network-state>
+ * this general guide</a> on how to use NetworkCapabilities and related classes.
+ *
+ * <p>NetworkCapabilities represent what a network can do and what its
+ * characteristics are like. The principal attribute of NetworkCapabilities
+ * is in the capabilities bits, which are checked with
+ * {@link #hasCapability(int)}. See the list of capabilities and each
+ * capability for a description of what it means.
+ *
+ * <p>Some prime examples include {@code NET_CAPABILITY_MMS}, which means that the
+ * network is capable of sending MMS. A network without this capability
+ * is not capable of sending MMS.
+ * <p>The {@code NET_CAPABILITY_INTERNET} capability means that the network is
+ * configured to reach the general Internet. It may or may not actually
+ * provide connectivity ; the {@code NET_CAPABILITY_VALIDATED} bit indicates that
+ * the system found actual connectivity to the general Internet the last
+ * time it checked. Apps interested in actual connectivity should usually
+ * look at both these capabilities.
+ * <p>The {@code NET_CAPABILITY_NOT_METERED} capability is set for networks that
+ * do not bill the user for consumption of bytes. Applications are
+ * encouraged to consult this to determine appropriate usage, and to
+ * limit usage of metered network where possible, including deferring
+ * big downloads until such a time that an unmetered network is connected.
+ * Also see {@link android.app.job.JobScheduler} to help with scheduling such
+ * downloads, in particular
+ * {@link android.app.job.JobInfo.Builder#setRequiredNetwork(NetworkRequest)}.
+ * <p>NetworkCapabilities contain a number of other capabilities that
+ * represent what modern networks can and can't do. Look up the individual
+ * capabilities in this class to learn about each of them.
+ *
+ * <p>NetworkCapabilities typically represent attributes that can apply to
+ * any network. The attributes that apply only to specific transports like
+ * cellular or Wi-Fi can be found in the specifier (for requestable attributes)
+ * or in the transport info (for non-requestable ones). See
+ * {@link #getNetworkSpecifier} and {@link #getTransportInfo}. An app would
+ * downcast these to the specific class for the transport they need if they
+ * are interested in transport-specific attributes. Also see
+ * {@link android.net.wifi.WifiNetworkSpecifier} or
+ * {@link android.net.wifi.WifiInfo} for some examples of each of these.
+ *
+ * <p>NetworkCapabilities also contains other attributes like the estimated
+ * upstream and downstream bandwidth and the specific transport of that
+ * network (e.g. {@link #TRANSPORT_CELLULAR}). Generally, apps should normally
+ * have little reason to check for the type of transport ; for example, to
+ * query whether a network costs money to the user, do not look at the
+ * transport, but instead look at the absence or presence of
+ * {@link #NET_CAPABILITY_NOT_METERED} which will correctly account for
+ * metered Wi-Fis and free of charge cell connections.
+ *
+ * <p>The system communicates with apps about connected networks and uses
+ * NetworkCapabilities to express these capabilities about these networks.
+ * Apps should register callbacks with the {@link ConnectivityManager#requestNetwork}
+ * or {@link ConnectivityManager#registerNetworkCallback} family of methods
+ * to learn about the capabilities of a network on a continuous basis
+ * and be able to react to changes to capabilities. For quick debugging Android also
+ * provides {@link ConnectivityManager#getNetworkCapabilities(Network)},
+ * but the dynamic nature of networking makes this ill-suited to production
+ * code since capabilities obtained in this way can go stale immediately.
+ *
+ * <p>Also see {@link NetworkRequest} which uses the same capabilities
+ * together with {@link ConnectivityManager#requestNetwork} for how to
+ * request the system brings up the kind of network your application needs.
  */
 public final class NetworkCapabilities implements Parcelable {
     private static final String TAG = "NetworkCapabilities";
@@ -621,11 +674,19 @@
 
     /**
      * Indicates that this network should be able to prioritize latency for the internet.
+     *
+     * Starting with {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, requesting this capability with
+     * {@link ConnectivityManager#requestNetwork} requires declaration in the self-certified
+     * network capabilities. See {@link NetworkRequest} for the self-certification documentation.
      */
     public static final int NET_CAPABILITY_PRIORITIZE_LATENCY = 34;
 
     /**
      * Indicates that this network should be able to prioritize bandwidth for the internet.
+     *
+     * Starting with {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, requesting this capability with
+     * {@link ConnectivityManager#requestNetwork} requires declaration in the self-certified
+     * network capabilities. See {@link NetworkRequest} for the self-certification documentation.
      */
     public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35;
 
@@ -696,10 +757,10 @@
                     NET_CAPABILITY_PARTIAL_CONNECTIVITY);
 
     /**
-     * Capabilities that are allowed for test networks. This list must be set so that it is safe
-     * for an unprivileged user to create a network with these capabilities via shell. As such,
-     * it must never contain capabilities that are generally useful to the system, such as
-     * INTERNET, IMS, SUPL, etc.
+     * Capabilities that are allowed for all test networks. This list must be set so that it is safe
+     * for an unprivileged user to create a network with these capabilities via shell. As such, it
+     * must never contain capabilities that are generally useful to the system, such as INTERNET,
+     * IMS, SUPL, etc.
      */
     private static final long TEST_NETWORKS_ALLOWED_CAPABILITIES =
             BitUtils.packBitList(
@@ -713,6 +774,14 @@
             NET_CAPABILITY_NOT_VCN_MANAGED);
 
     /**
+     * Extra allowed capabilities for test networks that do not have TRANSPORT_CELLULAR. Test
+     * networks with TRANSPORT_CELLULAR must not have those capabilities in order to mitigate
+     * the risk of being used by running apps.
+     */
+    private static final long TEST_NETWORKS_EXTRA_ALLOWED_CAPABILITIES_ON_NON_CELL =
+            BitUtils.packBitList(NET_CAPABILITY_CBS, NET_CAPABILITY_DUN, NET_CAPABILITY_RCS);
+
+    /**
      * Adds the given capability to this {@code NetworkCapability} instance.
      * Note that when searching for a network to satisfy a request, all capabilities
      * requested must be satisfied.
@@ -1065,14 +1134,22 @@
             mTransportTypes =
                     (originalTransportTypes & UNRESTRICTED_TEST_NETWORKS_ALLOWED_TRANSPORTS)
                             | (1 << TRANSPORT_TEST);
-
-            // SubIds are only allowed for Test Networks that only declare TRANSPORT_TEST.
-            setSubscriptionIds(originalSubIds);
         } else {
             // If the test network is restricted, then it may declare any transport.
             mTransportTypes = (originalTransportTypes | (1 << TRANSPORT_TEST));
         }
+
+        if (hasSingleTransport(TRANSPORT_TEST)) {
+            // SubIds are only allowed for Test Networks that only declare TRANSPORT_TEST.
+            setSubscriptionIds(originalSubIds);
+        }
+
         mNetworkCapabilities = originalCapabilities & TEST_NETWORKS_ALLOWED_CAPABILITIES;
+        if (!hasTransport(TRANSPORT_CELLULAR)) {
+            mNetworkCapabilities |=
+                    (originalCapabilities & TEST_NETWORKS_EXTRA_ALLOWED_CAPABILITIES_ON_NON_CELL);
+        }
+
         mNetworkSpecifier = originalSpecifier;
         mSignalStrength = originalSignalStrength;
         mTransportInfo = originalTransportInfo;
@@ -1109,6 +1186,7 @@
             TRANSPORT_LOWPAN,
             TRANSPORT_TEST,
             TRANSPORT_USB,
+            TRANSPORT_THREAD,
     })
     public @interface Transport { }
 
@@ -1160,10 +1238,15 @@
      */
     public static final int TRANSPORT_USB = 8;
 
+    /**
+     * Indicates this network uses a Thread transport.
+     */
+    public static final int TRANSPORT_THREAD = 9;
+
     /** @hide */
     public static final int MIN_TRANSPORT = TRANSPORT_CELLULAR;
     /** @hide */
-    public static final int MAX_TRANSPORT = TRANSPORT_USB;
+    public static final int MAX_TRANSPORT = TRANSPORT_THREAD;
 
     private static final int ALL_VALID_TRANSPORTS;
     static {
@@ -1188,7 +1271,8 @@
         "WIFI_AWARE",
         "LOWPAN",
         "TEST",
-        "USB"
+        "USB",
+        "THREAD",
     };
 
     /**
@@ -1264,6 +1348,18 @@
     }
 
     /**
+     * Gets the transports as an int. Internal callers only.
+     *
+     * Prefer getTransportTypes/hasTransportType if not immediately collapsing back into a scalar.
+     *
+     * @return a long integer representing the transport types.
+     * @hide
+     */
+    public long getTransportTypesInternal() {
+        return mTransportTypes;
+    }
+
+    /**
      * Sets all the transports set on this {@code NetworkCapability} instance.
      * This overwrites any existing transports.
      *
@@ -2069,30 +2165,14 @@
      * Returns a short but human-readable string of updates from an older set of capabilities.
      * @param old the old capabilities to diff from
      * @return a string fit for logging differences, or null if no differences.
-     *         this never returns the empty string.
+     *         this never returns the empty string. See BitUtils#describeDifferences.
      * @hide
      */
     @Nullable
     public String describeCapsDifferencesFrom(@Nullable final NetworkCapabilities old) {
         final long oldCaps = null == old ? 0 : old.mNetworkCapabilities;
-        final long changed = oldCaps ^ mNetworkCapabilities;
-        if (0 == changed) return null;
-        // If the control reaches here, there are changes (additions, removals, or both) so
-        // the code below is guaranteed to add something to the string and can't return "".
-        final long removed = oldCaps & changed;
-        final long added = mNetworkCapabilities & changed;
-        final StringBuilder sb = new StringBuilder();
-        if (0 != removed) {
-            sb.append("-");
-            appendStringRepresentationOfBitMaskToStringBuilder(sb, removed,
-                    NetworkCapabilities::capabilityNameOf, "-");
-        }
-        if (0 != added) {
-            sb.append("+");
-            appendStringRepresentationOfBitMaskToStringBuilder(sb, added,
-                    NetworkCapabilities::capabilityNameOf, "+");
-        }
-        return sb.toString();
+        return describeDifferences(oldCaps, mNetworkCapabilities,
+                NetworkCapabilities::capabilityNameOf);
     }
 
     /**
diff --git a/framework/src/android/net/NetworkInfo.java b/framework/src/android/net/NetworkInfo.java
index b7ec519..7aa9847 100644
--- a/framework/src/android/net/NetworkInfo.java
+++ b/framework/src/android/net/NetworkInfo.java
@@ -334,6 +334,24 @@
     }
 
     /**
+     * Indicates whether this network is suspended.
+     * @deprecated Apps should instead use the
+     *             {@link android.net.ConnectivityManager.NetworkCallback} API to
+     *             learn about connectivity changes. See
+     *             {@link ConnectivityManager#registerDefaultNetworkCallback} and
+     *             {@link ConnectivityManager#registerNetworkCallback}. These will
+     *             give a more accurate picture of the connectivity state of
+     *             the device and let apps react more easily and quickly to changes.
+     * @hide
+     */
+    @Deprecated
+    public boolean isSuspended() {
+        synchronized (this) {
+            return mState == State.SUSPENDED;
+        }
+    }
+
+    /**
      * Indicates whether network connectivity is possible. A network is unavailable
      * when a persistent or semi-persistent condition prevents the possibility
      * of connecting to that network. Examples include
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index b7a6076..6c351d0 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -54,9 +54,92 @@
 import java.util.Set;
 
 /**
- * Defines a request for a network, made through {@link NetworkRequest.Builder} and used
- * to request a network via {@link ConnectivityManager#requestNetwork} or listen for changes
- * via {@link ConnectivityManager#registerNetworkCallback}.
+ * An object describing a network that the application is interested in.
+ *
+ * <p>@see <a href="https://developer.android.com/training/basics/network-ops/reading-network-state>
+ * this general guide</a> on how to use NetworkCapabilities and related classes.
+ *
+ * NetworkRequest defines a request for a network, made through
+ * {@link NetworkRequest.Builder} and used to request a network via
+ * {@link ConnectivityManager#requestNetwork} or to listen for changes
+ * via the {@link ConnectivityManager#registerNetworkCallback} family of
+ * functions.
+ *
+ * <p>{@link ConnectivityManager#requestNetwork} will try to find a connected
+ * network matching the NetworkRequest, and return it if there is one.
+ * As long as the request is outstanding, the system will try to find the best
+ * possible network that matches the request. The request will keep up the
+ * currently best connected network, and if a better one is found (e.g. cheaper
+ * or faster) the system will bring up that better network to better serve the
+ * request. A request filed with {@link ConnectivityManager#requestNetwork} will
+ * only match one network at a time (the one the system thinks is best), even if
+ * other networks can match the request that are being kept up by other requests.
+ *
+ * For example, an application needing a network with
+ * {@link NetworkCapabilities#NET_CAPABILITY_INTERNET} should use
+ * {@link ConnectivityManager#requestNetwork} to request the system keeps one up.
+ * A general cellular network can satisfy this request, but if the system finds
+ * a free Wi-Fi network which is expected to be faster, it will try and connect
+ * to that Wi-Fi network and switch the request over to it once it is connected.
+ * The cell network may stay connected if there are outstanding requests (from
+ * the same app or from other apps on the system) that match the cell network
+ * but not the Wi-Fi network, such as a request with {@link NetworkCapabilities#NET_CAPABILITY_MMS}.
+ *
+ * When a network is no longer needed to serve any request, the system can
+ * tear it down at any time and usually does so immediately, so make sure to
+ * keep up requests for the networks your app needs.
+ *
+ * <p>By contrast, requests filed with {@link ConnectivityManager#registerNetworkCallback}
+ * will receive callbacks for all matching networks, and will not cause the system to
+ * keep up the networks they match. Use this to listen to networks that the device is
+ * connected to, but that you don't want the system to keep up for your use case.
+ *
+ * <p>Applications build a NetworkRequest and pass it to one of the
+ * {@link ConnectivityManager} methods above together with a
+ * {@link ConnectivityManager.NetworkCallback} object. The callback
+ * will then start receiving method calls about networks that match
+ * the request.
+ *
+ * <p>Networks are brought up and/or matched according to the capabilities
+ * set in the builder. For example, a request with
+ * {@link NetworkCapabilities#NET_CAPABILITY_MMS} lets the system match
+ * and/or bring up a network that is capable to send MMS. A request with
+ * {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED} matches a network
+ * that doesn't charge the user for usage. See
+ * {@link NetworkCapabilities} for a list of capabilities and their
+ * description.
+ *
+ * <p>While all capabilities can be matched with the
+ * {@link ConnectivityManager#registerNetworkCallback} family of methods,
+ * not all capabilities can be used to request that the system brings
+ * up a network with {@link ConnectivityManager#requestNetwork}. For example,
+ * an application cannot use {@link ConnectivityManager#requestNetwork} to
+ * ask the system to bring up a network with
+ * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}, because the
+ * system won't know if a network has a captive portal before it connects
+ * to that network. Similarly, some capabilities may require a specific
+ * permission or privilege to be requested.
+ *
+ * Look up the specific capability and the {@link ConnectivityManager#requestNetwork}
+ * method for limitations applicable to each capability.
+ *
+ * <p>Also, starting with {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, some capabilities
+ * require the application to self-certify by explicitly adding the
+ * {@link android.content.pm.PackageManager#PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES}
+ * property in the AndroidManifest.xml, which points to an XML resource file. In the
+ * XML resource file, the application declares what kind of network capabilities the application
+ * wants to have.
+ *
+ * Here is an example self-certification XML resource file :
+ * <pre>
+ *  {@code
+ *  <network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+ *     <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+ *     <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+ * </network-capabilities-declaration>
+ *  }
+ *  </pre>
+ * Look up the specific capability to learn whether its usage requires this self-certification.
  */
 public class NetworkRequest implements Parcelable {
     /**
diff --git a/framework/src/android/net/SocketKeepalive.java b/framework/src/android/net/SocketKeepalive.java
index 57cf5e3..f915e72 100644
--- a/framework/src/android/net/SocketKeepalive.java
+++ b/framework/src/android/net/SocketKeepalive.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.PRIVILEGED_APPS;
+
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -63,6 +65,12 @@
     public static final int SUCCESS = 0;
 
     /**
+     * Success when trying to suspend.
+     * @hide
+     */
+    public static final int SUCCESS_PAUSED = 1;
+
+    /**
      * No keepalive. This should only be internally as it indicates There is no keepalive.
      * It should not propagate to applications.
      * @hide
@@ -141,9 +149,7 @@
     public static final int ERROR_INSUFFICIENT_RESOURCES = -32;
 
     /**
-     * There was no such slot. This should only be internally as it indicates
-     * a programming error in the system server. It should not propagate to
-     * applications.
+     * There was no such slot, or no keepalive running on this slot.
      * @hide
      */
     @SystemApi
@@ -174,6 +180,27 @@
     public @interface KeepaliveEvent {}
 
     /**
+     * Whether the system automatically toggles keepalive when no TCP connection is open on the VPN.
+     *
+     * If this flag is present, the system will monitor the VPN(s) running on top of the specified
+     * network for open TCP connections. When no such connections are open, it will turn off the
+     * keepalives to conserve battery power. When there is at least one such connection it will
+     * turn on the keepalives to make sure functionality is preserved.
+     *
+     * This only works with {@link NattSocketKeepalive}.
+     * @hide
+     */
+    @SystemApi
+    public static final int FLAG_AUTOMATIC_ON_OFF = 1 << 0;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "FLAG_"}, flag = true, value = {
+            FLAG_AUTOMATIC_ON_OFF
+    })
+    public @interface StartFlags {}
+
+    /**
      * The minimum interval in seconds between keepalive packet transmissions.
      *
      * @hide
@@ -226,9 +253,6 @@
     @NonNull protected final Executor mExecutor;
     /** @hide */
     @NonNull protected final ISocketKeepaliveCallback mCallback;
-    // TODO: remove slot since mCallback could be used to identify which keepalive to stop.
-    /** @hide */
-    @Nullable protected Integer mSlot;
 
     /** @hide */
     public SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network,
@@ -240,11 +264,10 @@
         mExecutor = executor;
         mCallback = new ISocketKeepaliveCallback.Stub() {
             @Override
-            public void onStarted(int slot) {
+            public void onStarted() {
                 final long token = Binder.clearCallingIdentity();
                 try {
                     mExecutor.execute(() -> {
-                        mSlot = slot;
                         callback.onStarted();
                     });
                 } finally {
@@ -253,11 +276,22 @@
             }
 
             @Override
+            public void onResumed() {
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mExecutor.execute(() -> {
+                        callback.onResumed();
+                    });
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+
+            @Override
             public void onStopped() {
                 final long token = Binder.clearCallingIdentity();
                 try {
                     executor.execute(() -> {
-                        mSlot = null;
                         callback.onStopped();
                     });
                 } finally {
@@ -266,11 +300,22 @@
             }
 
             @Override
+            public void onPaused() {
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> {
+                        callback.onPaused();
+                    });
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+
+            @Override
             public void onError(int error) {
                 final long token = Binder.clearCallingIdentity();
                 try {
                     executor.execute(() -> {
-                        mSlot = null;
                         callback.onError(error);
                     });
                 } finally {
@@ -283,7 +328,6 @@
                 final long token = Binder.clearCallingIdentity();
                 try {
                     executor.execute(() -> {
-                        mSlot = null;
                         callback.onDataReceived();
                     });
                 } finally {
@@ -294,13 +338,15 @@
     }
 
     /**
-     * Request that keepalive be started with the given {@code intervalSec}. See
-     * {@link SocketKeepalive}. If the remote binder dies, or the binder call throws an exception
-     * when invoking start or stop of the {@link SocketKeepalive}, a {@link RemoteException} will be
-     * thrown into the {@code executor}. This is typically not important to catch because the remote
-     * party is the system, so if it is not in shape to communicate through binder the system is
-     * probably going down anyway. If the caller cares regardless, it can use a custom
-     * {@link Executor} to catch the {@link RemoteException}.
+     * Request that keepalive be started with the given {@code intervalSec}.
+     *
+     * See {@link SocketKeepalive}. If the remote binder dies, or the binder call throws an
+     * exception when invoking start or stop of the {@link SocketKeepalive}, a
+     * {@link RuntimeException} caused by a {@link RemoteException} will be thrown into the
+     * {@link Executor}. This is typically not important to catch because the remote party is
+     * the system, so if it is not in shape to communicate through binder the system is going
+     * down anyway. If the caller still cares, it can use a custom {@link Executor} to catch the
+     * {@link RuntimeException}.
      *
      * @param intervalSec The target interval in seconds between keepalive packet transmissions.
      *                    The interval should be between 10 seconds and 3600 seconds, otherwise
@@ -308,11 +354,39 @@
      */
     public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC)
             int intervalSec) {
-        startImpl(intervalSec);
+        startImpl(intervalSec, 0 /* flags */, null /* underpinnedNetwork */);
+    }
+
+    /**
+     * Request that keepalive be started with the given {@code intervalSec}.
+     *
+     * See {@link SocketKeepalive}. If the remote binder dies, or the binder call throws an
+     * exception when invoking start or stop of the {@link SocketKeepalive}, a
+     * {@link RuntimeException} caused by a {@link RemoteException} will be thrown into the
+     * {@link Executor}. This is typically not important to catch because the remote party is
+     * the system, so if it is not in shape to communicate through binder the system is going
+     * down anyway. If the caller still cares, it can use a custom {@link Executor} to catch the
+     * {@link RuntimeException}.
+     *
+     * @param intervalSec The target interval in seconds between keepalive packet transmissions.
+     *                    The interval should be between 10 seconds and 3600 seconds. Otherwise,
+     *                    the supplied {@link Callback} will see a call to
+     *                    {@link Callback#onError(int)} with {@link #ERROR_INVALID_INTERVAL}.
+     * @param flags Flags to enable/disable available options on this keepalive.
+     * @param underpinnedNetwork an optional network running over mNetwork that this
+     *                           keepalive is intended to keep up, e.g. an IPSec
+     *                           tunnel running over mNetwork.
+     * @hide
+     */
+    @SystemApi(client = PRIVILEGED_APPS)
+    public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC)
+            int intervalSec, @StartFlags int flags, @Nullable Network underpinnedNetwork) {
+        startImpl(intervalSec, flags, underpinnedNetwork);
     }
 
     /** @hide */
-    protected abstract void startImpl(int intervalSec);
+    protected abstract void startImpl(int intervalSec, @StartFlags int flags,
+            Network underpinnedNetwork);
 
     /**
      * Requests that keepalive be stopped. The application must wait for {@link Callback#onStopped}
@@ -346,8 +420,18 @@
     public static class Callback {
         /** The requested keepalive was successfully started. */
         public void onStarted() {}
+        /**
+         * The keepalive was resumed by the system after being suspended.
+         * @hide
+         **/
+        public void onResumed() {}
         /** The keepalive was successfully stopped. */
         public void onStopped() {}
+        /**
+         * The keepalive was paused by the system because it's not necessary right now.
+         * @hide
+         **/
+        public void onPaused() {}
         /** An error occurred. */
         public void onError(@ErrorCode int error) {}
         /** The keepalive on a TCP socket was stopped because the socket received data. This is
diff --git a/framework/src/android/net/TcpSocketKeepalive.java b/framework/src/android/net/TcpSocketKeepalive.java
index 7131784..696889f 100644
--- a/framework/src/android/net/TcpSocketKeepalive.java
+++ b/framework/src/android/net/TcpSocketKeepalive.java
@@ -50,7 +50,17 @@
      *   acknowledgement.
      */
     @Override
-    protected void startImpl(int intervalSec) {
+    protected void startImpl(int intervalSec, int flags, Network underpinnedNetwork) {
+        if (0 != flags) {
+            throw new IllegalArgumentException("Illegal flag value for "
+                    + this.getClass().getSimpleName() + " : " + flags);
+        }
+
+        if (underpinnedNetwork != null) {
+            throw new IllegalArgumentException("Illegal underpinned network for "
+                    + this.getClass().getSimpleName() + " : " + underpinnedNetwork);
+        }
+
         mExecutor.execute(() -> {
             try {
                 mService.startTcpKeepalive(mNetwork, mPfd, intervalSec, mCallback);
@@ -65,9 +75,7 @@
     protected void stopImpl() {
         mExecutor.execute(() -> {
             try {
-                if (mSlot != null) {
-                    mService.stopKeepalive(mNetwork, mSlot);
-                }
+                mService.stopKeepalive(mCallback);
             } catch (RemoteException e) {
                 Log.e(TAG, "Error stopping packet keepalive: ", e);
                 throw e.rethrowFromSystemServer();
diff --git a/framework/src/android/net/TestNetworkManager.java b/framework/src/android/net/TestNetworkManager.java
index b64299f..416c6de 100644
--- a/framework/src/android/net/TestNetworkManager.java
+++ b/framework/src/android/net/TestNetworkManager.java
@@ -260,7 +260,7 @@
     /**
      * Create a tap interface with or without carrier for testing purposes.
      *
-     * Note: setting carrierUp = false is not supported until kernel version 5.0.
+     * Note: setting carrierUp = false is not supported until kernel version 6.0.
      *
      * @param carrierUp whether the created interface has a carrier or not.
      * @param bringUp whether to bring up the interface before returning it.
@@ -280,6 +280,8 @@
     /**
      * Create a tap interface for testing purposes.
      *
+     * Note: setting carrierUp = false is not supported until kernel version 6.0.
+     *
      * @param carrierUp whether the created interface has a carrier or not.
      * @param bringUp whether to bring up the interface before returning it.
      * @param disableIpv6ProvisioningDelay whether to disable DAD and RS delay.
diff --git a/framework/src/android/net/VpnTransportInfo.java b/framework/src/android/net/VpnTransportInfo.java
index ebad477..6bb00c8 100644
--- a/framework/src/android/net/VpnTransportInfo.java
+++ b/framework/src/android/net/VpnTransportInfo.java
@@ -52,6 +52,8 @@
 
     private final boolean mBypassable;
 
+    private final boolean mLongLivedTcpConnectionsExpensive;
+
     // TODO: Refer to Build.VERSION_CODES when it's available in every branch.
     private static final int UPSIDE_DOWN_CAKE = 34;
 
@@ -70,11 +72,12 @@
     @SystemApi(client = MODULE_LIBRARIES)
     public VpnTransportInfo makeCopy(@RedactionType long redactions) {
         return new VpnTransportInfo(mType,
-            ((redactions & REDACT_FOR_NETWORK_SETTINGS) != 0) ? null : mSessionId, mBypassable);
+            ((redactions & REDACT_FOR_NETWORK_SETTINGS) != 0) ? null : mSessionId,
+            mBypassable, mLongLivedTcpConnectionsExpensive);
     }
 
     /**
-     * @deprecated please use {@link VpnTransportInfo(int,String,boolean)} instead.
+     * @deprecated please use {@link VpnTransportInfo(int,String,boolean,boolean)}.
      * @hide
      */
     @Deprecated
@@ -83,17 +86,22 @@
         // When the module runs on older SDKs, |bypassable| will always be false since the old Vpn
         // code will call this constructor. For Settings VPNs, this is always correct as they are
         // never bypassable. For VpnManager and VpnService types, this may be wrong since both of
-        // them have a choice. However, on these SDKs VpnTransportInfo#getBypassable is not
+        // them have a choice. However, on these SDKs VpnTransportInfo#isBypassable is not
         // available anyway, so this should be harmless. False is a better choice than true here
         // regardless because it is the default value for both VpnManager and VpnService if the app
         // does not do anything about it.
-        this(type, sessionId, false /* bypassable */);
+        this(type, sessionId, false /* bypassable */, false /* longLivedTcpConnectionsExpensive */);
     }
 
-    public VpnTransportInfo(int type, @Nullable String sessionId, boolean bypassable) {
+    /**
+     * Construct a new VpnTransportInfo object.
+     */
+    public VpnTransportInfo(int type, @Nullable String sessionId, boolean bypassable,
+            boolean longLivedTcpConnectionsExpensive) {
         this.mType = type;
         this.mSessionId = sessionId;
         this.mBypassable = bypassable;
+        this.mLongLivedTcpConnectionsExpensive = longLivedTcpConnectionsExpensive;
     }
 
     /**
@@ -103,7 +111,7 @@
      * {@code UnsupportedOperationException} if called.
      */
     @RequiresApi(UPSIDE_DOWN_CAKE)
-    public boolean getBypassable() {
+    public boolean isBypassable() {
         if (!SdkLevel.isAtLeastU()) {
             throw new UnsupportedOperationException("Not supported before U");
         }
@@ -112,6 +120,37 @@
     }
 
     /**
+     * Returns whether long-lived TCP connections are expensive on the VPN network.
+     *
+     * If there are long-lived TCP connections over the VPN, over some networks the
+     * VPN needs to regularly send packets to keep the network alive to keep these
+     * connections working, which wakes up the device radio. On some networks, this
+     * can become extremely expensive in terms of battery. The system knows to send
+     * these keepalive packets only when necessary, i.e. when there are long-lived
+     * TCP connections opened over the VPN, meaning on these networks establishing
+     * a long-lived TCP connection will have a very noticeable impact on battery
+     * life.
+     *
+     * VPNs can be bypassable or not. When the VPN is not bypassable, the user has
+     * expressed explicit intent to have no connection outside of the VPN, so even
+     * privileged apps with permission to bypass non-bypassable VPNs should not do
+     * so. See {@link #isBypassable()}.
+     * For bypassable VPNs however, the user expects apps choose reasonable tradeoffs
+     * about whether they use the VPN.
+     *
+     * Components that establish long-lived, encrypted TCP connections are encouraged
+     * to look up this value to decide whether to open their connection over a VPN
+     * or to bypass it. While VPNs do not typically provide privacy or security
+     * benefits to encrypted connections, the user generally still expects the
+     * connections to choose to use the VPN by default, but also do not expect this
+     * comes at the price of drastically reduced battery life. This method provides
+     * a hint about whether the battery cost of opening such a connection is high.
+     */
+    public boolean areLongLivedTcpConnectionsExpensive() {
+        return mLongLivedTcpConnectionsExpensive;
+    }
+
+    /**
      * Returns the session Id of this VpnTransportInfo.
      * @hide
      */
@@ -134,18 +173,21 @@
 
         VpnTransportInfo that = (VpnTransportInfo) o;
         return (this.mType == that.mType) && TextUtils.equals(this.mSessionId, that.mSessionId)
-                && (this.mBypassable == that.mBypassable);
+                && (this.mBypassable == that.mBypassable)
+                && (this.mLongLivedTcpConnectionsExpensive
+                == that.mLongLivedTcpConnectionsExpensive);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mType, mSessionId, mBypassable);
+        return Objects.hash(mType, mSessionId, mBypassable, mLongLivedTcpConnectionsExpensive);
     }
 
     @Override
     public String toString() {
-        return String.format("VpnTransportInfo{type=%d, sessionId=%s, bypassable=%b}",
-                mType, mSessionId, mBypassable);
+        return String.format("VpnTransportInfo{type=%d, sessionId=%s, bypassable=%b "
+                        + "longLivedTcpConnectionsExpensive=%b}",
+                mType, mSessionId, mBypassable, mLongLivedTcpConnectionsExpensive);
     }
 
     @Override
@@ -158,12 +200,14 @@
         dest.writeInt(mType);
         dest.writeString(mSessionId);
         dest.writeBoolean(mBypassable);
+        dest.writeBoolean(mLongLivedTcpConnectionsExpensive);
     }
 
     public static final @NonNull Creator<VpnTransportInfo> CREATOR =
             new Creator<VpnTransportInfo>() {
         public VpnTransportInfo createFromParcel(Parcel in) {
-            return new VpnTransportInfo(in.readInt(), in.readString(), in.readBoolean());
+            return new VpnTransportInfo(
+                    in.readInt(), in.readString(), in.readBoolean(), in.readBoolean());
         }
         public VpnTransportInfo[] newArray(int size) {
             return new VpnTransportInfo[size];
diff --git a/framework/src/android/net/apf/ApfCapabilities.java b/framework/src/android/net/apf/ApfCapabilities.java
index 64f14a1..fae2499 100644
--- a/framework/src/android/net/apf/ApfCapabilities.java
+++ b/framework/src/android/net/apf/ApfCapabilities.java
@@ -19,9 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
-import android.content.Context;
-import android.content.res.Resources;
-import android.net.ConnectivityResources;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -36,8 +33,6 @@
  */
 @SystemApi
 public final class ApfCapabilities implements Parcelable {
-    private static ConnectivityResources sResources;
-
     /**
      * Version of APF instruction set supported for packet filtering. 0 indicates no support for
      * packet filtering using APF programs.
@@ -67,15 +62,6 @@
         apfPacketFormat = in.readInt();
     }
 
-    @NonNull
-    private static synchronized ConnectivityResources getResources(@NonNull Context ctx) {
-        if (sResources == null)  {
-            sResources = new ConnectivityResources(ctx);
-        }
-        return sResources;
-    }
-
-
     @Override
     public int describeContents() {
         return 0;
diff --git a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
new file mode 100644
index 0000000..dfe5867
--- /dev/null
+++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
@@ -0,0 +1,89 @@
+/*
+ * 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.connectivity;
+
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.EnabledSince;
+import android.os.Build;
+
+/**
+ * The class contains all CompatChanges for the Connectivity module.
+ *
+ * <p>This is the centralized place for the CompatChanges used in the Connectivity module.
+ * Putting all the CompatChanges in single place makes it possible to manage them under a single
+ * platform_compat_config.
+ * @hide
+ */
+public final class ConnectivityCompatChanges {
+
+    /**
+     * The {@link android.net.LinkProperties#getRoutes()} now can contain excluded as well as
+     * included routes. Use {@link android.net.RouteInfo#getType()} to determine route type.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2)
+    public static final long EXCLUDED_ROUTES = 186082280;
+
+    /**
+     * When enabled, apps targeting < Android 12 are considered legacy for
+     * the NSD native daemon.
+     * The platform will only keep the daemon running as long as there are
+     * any legacy apps connected.
+     *
+     * After Android 12, direct communication with the native daemon might not work since the native
+     * daemon won't always stay alive. Using the NSD APIs from NsdManager as the replacement is
+     * recommended.
+     * Another alternative could be bundling your own mdns solutions instead of
+     * depending on the system mdns native daemon.
+     *
+     * This compatibility change applies to Android 13 and later only. To toggle behavior on
+     * Android 12 and Android 12L, use RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
+    // This was a platform change ID with value 191844585L before T
+    public static final long RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER = 235355681L;
+
+    /**
+     * The self certified capabilities check should be enabled after android 13.
+     *
+     * <p> See {@link android.net.NetworkCapabilities} for more details.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    public static final long ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION = 266524688;
+
+    /**
+     * Apps targeting < Android 14 use a legacy NSD backend.
+     *
+     * The legacy apps use a legacy native daemon as NsdManager backend, but other apps use a
+     * platform-integrated mDNS implementation as backend.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    public static final long ENABLE_PLATFORM_MDNS_BACKEND = 270306772L;
+    private ConnectivityCompatChanges() {
+    }
+}
diff --git a/framework/src/android/net/connectivity/TiramisuConnectivityInternalApiUtil.java b/framework/src/android/net/connectivity/TiramisuConnectivityInternalApiUtil.java
new file mode 100644
index 0000000..d65858f
--- /dev/null
+++ b/framework/src/android/net/connectivity/TiramisuConnectivityInternalApiUtil.java
@@ -0,0 +1,48 @@
+/*
+ * 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.connectivity;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.os.Build;
+import android.os.IBinder;
+
+import androidx.annotation.RequiresApi;
+
+/**
+ * Utility providing limited access to module-internal APIs which are only available on Android T+,
+ * as this class is only in the bootclasspath on T+ as part of framework-connectivity.
+ *
+ * R+ module components like Tethering cannot depend on all hidden symbols from
+ * framework-connectivity. They only have access to stable API stubs where newer APIs can be
+ * accessed after an API level check (enforced by the linter), or to limited hidden symbols in this
+ * class which is also annotated with @RequiresApi (so API level checks are also enforced by the
+ * linter).
+ * @hide
+ */
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+public class TiramisuConnectivityInternalApiUtil {
+
+    /**
+     * Get a service binder token for
+     * {@link com.android.server.connectivity.wear.CompanionDeviceManagerProxyService}.
+     */
+    public static IBinder getCompanionDeviceManagerProxyService(Context ctx) {
+        final ConnectivityManager cm = ctx.getSystemService(ConnectivityManager.class);
+        return cm.getCompanionDeviceManagerProxyService();
+    }
+}
diff --git a/framework/src/android/net/util/KeepaliveUtils.java b/framework/src/android/net/util/KeepaliveUtils.java
index 8d7a0b3..07b8c45 100644
--- a/framework/src/android/net/util/KeepaliveUtils.java
+++ b/framework/src/android/net/util/KeepaliveUtils.java
@@ -18,11 +18,8 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.content.res.Resources;
-import android.net.ConnectivityResources;
+import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
-import android.text.TextUtils;
-import android.util.AndroidRuntimeException;
 
 /**
  * Collection of utilities for socket keepalive offload.
@@ -33,64 +30,20 @@
 
     public static final String TAG = "KeepaliveUtils";
 
-    public static class KeepaliveDeviceConfigurationException extends AndroidRuntimeException {
-        public KeepaliveDeviceConfigurationException(final String msg) {
-            super(msg);
-        }
-    }
-
     /**
      * Read supported keepalive count for each transport type from overlay resource. This should be
      * used to create a local variable store of resource customization, and use it as the input for
-     * {@link getSupportedKeepalivesForNetworkCapabilities}.
+     * {@link #getSupportedKeepalivesForNetworkCapabilities}.
      *
      * @param context The context to read resource from.
      * @return An array of supported keepalive count for each transport type.
+     * @deprecated This is used by CTS 13, but can be removed after switching it to
+     * {@link ConnectivityManager#getSupportedKeepalives()}.
      */
     @NonNull
+    @Deprecated
     public static int[] getSupportedKeepalives(@NonNull Context context) {
-        String[] res = null;
-        try {
-            final ConnectivityResources connRes = new ConnectivityResources(context);
-            // TODO: use R.id.config_networkSupportedKeepaliveCount directly
-            final int id = connRes.get().getIdentifier("config_networkSupportedKeepaliveCount",
-                    "array", connRes.getResourcesContext().getPackageName());
-            res = new ConnectivityResources(context).get().getStringArray(id);
-        } catch (Resources.NotFoundException unused) {
-        }
-        if (res == null) throw new KeepaliveDeviceConfigurationException("invalid resource");
-
-        final int[] ret = new int[NetworkCapabilities.MAX_TRANSPORT + 1];
-        for (final String row : res) {
-            if (TextUtils.isEmpty(row)) {
-                throw new KeepaliveDeviceConfigurationException("Empty string");
-            }
-            final String[] arr = row.split(",");
-            if (arr.length != 2) {
-                throw new KeepaliveDeviceConfigurationException("Invalid parameter length");
-            }
-
-            int transport;
-            int supported;
-            try {
-                transport = Integer.parseInt(arr[0]);
-                supported = Integer.parseInt(arr[1]);
-            } catch (NumberFormatException e) {
-                throw new KeepaliveDeviceConfigurationException("Invalid number format");
-            }
-
-            if (!NetworkCapabilities.isValidTransport(transport)) {
-                throw new KeepaliveDeviceConfigurationException("Invalid transport " + transport);
-            }
-
-            if (supported < 0) {
-                throw new KeepaliveDeviceConfigurationException(
-                        "Invalid supported count " + supported + " for "
-                                + NetworkCapabilities.transportNameOf(transport));
-            }
-            ret[transport] = supported;
-        }
-        return ret;
+        return context.getSystemService(ConnectivityManager.class).getSupportedKeepalives();
     }
 
     /**
diff --git a/nearby/framework/Android.bp b/nearby/framework/Android.bp
index e223b54..278f823 100644
--- a/nearby/framework/Android.bp
+++ b/nearby/framework/Android.bp
@@ -32,10 +32,10 @@
 
 filegroup {
     name: "framework-nearby-sources",
+    defaults: ["framework-sources-module-defaults"],
     srcs: [
         ":framework-nearby-java-sources",
     ],
-    visibility: ["//frameworks/base"],
 }
 
 // Build of only framework-nearby (not as part of connectivity) for
@@ -45,6 +45,7 @@
     srcs: [":framework-nearby-java-sources"],
     sdk_version: "module_current",
     libs: [
+        "androidx.annotation_annotation",
         "framework-annotations-lib",
         "framework-bluetooth",
     ],
diff --git a/nearby/halfsheet/Android.bp b/nearby/halfsheet/Android.bp
index 2d0d327..8011dc6 100644
--- a/nearby/halfsheet/Android.bp
+++ b/nearby/halfsheet/Android.bp
@@ -27,7 +27,7 @@
     certificate: ":com.android.nearby.halfsheetcertificate",
     libs: [
         "framework-bluetooth",
-        "framework-connectivity-t",
+        "framework-connectivity-t.impl",
         "nearby-service-string",
     ],
     static_libs: [
diff --git a/nearby/halfsheet/res/values-ky/strings.xml b/nearby/halfsheet/res/values-ky/strings.xml
index 812e0e8..b0dfe20 100644
--- a/nearby/halfsheet/res/values-ky/strings.xml
+++ b/nearby/halfsheet/res/values-ky/strings.xml
@@ -25,5 +25,5 @@
     <string name="paring_action_save" msgid="6259357442067880136">"Сактоо"</string>
     <string name="paring_action_connect" msgid="4801102939608129181">"Туташуу"</string>
     <string name="paring_action_launch" msgid="8940808384126591230">"Жөндөө"</string>
-    <string name="paring_action_settings" msgid="424875657242864302">"Жөндөөлөр"</string>
+    <string name="paring_action_settings" msgid="424875657242864302">"Параметрлер"</string>
 </resources>
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
index 2a38b8a..07e5776 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.nearby.halfsheet;
 
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+
 import static com.android.nearby.halfsheet.fragment.DevicePairingFragment.APP_LAUNCH_FRAGMENT_TYPE;
 import static com.android.server.nearby.common.bluetooth.fastpair.FastPairConstants.EXTRA_MODEL_ID;
 import static com.android.server.nearby.common.fastpair.service.UserActionHandlerBase.EXTRA_MAC_ADDRESS;
@@ -226,7 +228,8 @@
                                     EXTRA_HALF_SHEET_IS_RETROACTIVE,
                                     getIntent().getBooleanExtra(EXTRA_HALF_SHEET_IS_RETROACTIVE,
                                             false))
-                            .putExtra(EXTRA_MAC_ADDRESS, mScanFastPairStoreItem.getAddress()));
+                            .putExtra(EXTRA_MAC_ADDRESS, mScanFastPairStoreItem.getAddress()),
+                    ACCESS_FINE_LOCATION);
         }
     }
 
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java
index 467997c..2f1e90a 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java
@@ -31,6 +31,13 @@
         context.sendBroadcast(intent);
     }
 
+    /**
+     * Helps send a broadcast with specified receiver permission.
+     */
+    public static void sendBroadcast(Context context, Intent intent, String receiverPermission) {
+        context.sendBroadcast(intent, receiverPermission);
+    }
+
     private BroadcastUtils() {
     }
 }
diff --git a/nearby/tests/cts/fastpair/AndroidManifest.xml b/nearby/tests/cts/fastpair/AndroidManifest.xml
index 96e2783..9e1ec70 100644
--- a/nearby/tests/cts/fastpair/AndroidManifest.xml
+++ b/nearby/tests/cts/fastpair/AndroidManifest.xml
@@ -30,7 +30,5 @@
   <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
       android:targetPackage="android.nearby.cts"
       android:label="CTS tests for android.nearby Fast Pair">
-    <meta-data android:name="listener"
-        android:value="com.android.cts.runner.CtsTestRunListener"/>
   </instrumentation>
 </manifest>
diff --git a/nearby/tests/integration/untrusted/src/androidx/test/uiautomator/LogcatWaitMixin.java b/nearby/tests/integration/untrusted/src/androidx/test/uiautomator/LogcatWaitMixin.java
index 86e39dc..8b5ac12 100644
--- a/nearby/tests/integration/untrusted/src/androidx/test/uiautomator/LogcatWaitMixin.java
+++ b/nearby/tests/integration/untrusted/src/androidx/test/uiautomator/LogcatWaitMixin.java
@@ -54,7 +54,7 @@
             @NonNull String specificLog, @NonNull Date startTime) {
         return new Condition<UiDevice, Boolean>() {
             @Override
-            Boolean apply(UiDevice device) {
+            public Boolean apply(UiDevice device) {
                 String logcatLogs;
                 try {
                     logcatLogs = device.executeShellCommand("logcat -v time -v year -d");
diff --git a/nearby/tests/multidevices/README.md b/nearby/tests/multidevices/README.md
index b64667c..9d086de 100644
--- a/nearby/tests/multidevices/README.md
+++ b/nearby/tests/multidevices/README.md
@@ -43,14 +43,24 @@
 *   Adjust Bluetooth profile configurations. \
     The Fast Pair provider simulator is an opposite role to the seeker. It needs
     to enable/disable the following Bluetooth profile:
-    *   Disable A2DP (profile_supported_a2dp)
-    *   Disable the AVRCP controller (profile_supported_avrcp_controller)
-    *   Enable A2DP sink (profile_supported_a2dp_sink)
-    *   Enable the HFP client connection service (profile_supported_hfpclient,
-        hfp_client_connection_service_enabled)
-    *   Enable the AVRCP target (profile_supported_avrcp_target)
-    *   Enable the automatic audio focus request
-        (a2dp_sink_automatically_request_audio_focus)
+    *   Disable A2DP source (bluetooth.profile.a2dp.source.enabled)
+    *   Enable A2DP sink (bluetooth.profile.a2dp.sink.enabled)
+    *   Disable the AVRCP controller (bluetooth.profile.avrcp.controller.enabled)
+    *   Enable the AVRCP target (bluetooth.profile.avrcp.target.enabled)
+    *   Enable the HFP service (bluetooth.profile.hfp.ag.enabled, bluetooth.profile.hfp.hf.enabled)
+
+```makefile
+# The Bluetooth profiles that Fast Pair provider simulator expect to have enabled.
+PRODUCT_PRODUCT_PROPERTIES += \
+    bluetooth.device.default_name=FastPairProviderSimulator \
+    bluetooth.profile.a2dp.source.enabled=false \
+    bluetooth.profile.a2dp.sink.enabled=true \
+    bluetooth.profile.avrcp.controller.enabled=false \
+    bluetooth.profile.avrcp.target.enabled=true \
+    bluetooth.profile.hfp.ag.enabled=true \
+    bluetooth.profile.hfp.hf.enabled=true
+```
+
 *   Adjust Bluetooth TX power limitation in Bluetooth module and disable the
     Fast Pair in Google Play service (aka GMS)
 
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/MainActivity.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/MainActivity.java
index e916c53..75fafb0 100644
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/MainActivity.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/MainActivity.java
@@ -657,9 +657,7 @@
 
             int desiredIoCapability = getIoCapabilityFromModelId(modelId);
 
-            mBluetoothController.setIoCapability(
-                    /*ioCapabilityClassic=*/ desiredIoCapability,
-                    /*ioCapabilityBLE=*/ desiredIoCapability);
+            mBluetoothController.setIoCapability(desiredIoCapability);
 
             runOnUiThread(() -> {
                 updateStringStatusView(
@@ -950,9 +948,7 @@
         }
 
         // Recover the IO capability.
-        mBluetoothController.setIoCapability(
-                /*ioCapabilityClassic=*/ IO_CAPABILITY_IO, /*ioCapabilityBLE=*/
-                IO_CAPABILITY_KBDISP);
+        mBluetoothController.setIoCapability(IO_CAPABILITY_IO);
 
         super.onDestroy();
     }
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothController.kt b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothController.kt
index 0cc0c92..345e8d2 100644
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothController.kt
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothController.kt
@@ -50,23 +50,16 @@
     }
 
     /**
-     * Sets the Input/Output capability of the device for both classic Bluetooth and BLE operations.
+     * Sets the Input/Output capability of the device for classic Bluetooth operations.
      * Note: In order to let changes take effect, this method will make sure the Bluetooth stack is
      * restarted by blocking calling thread.
      *
      * @param ioCapabilityClassic One of {@link #IO_CAPABILITY_IO}, {@link #IO_CAPABILITY_NONE},
      * ```
      *     {@link #IO_CAPABILITY_KBDISP} or more in {@link BluetoothAdapter}.
-     * @param ioCapabilityBLE
-     * ```
-     * One of {@link #IO_CAPABILITY_IO}, {@link #IO_CAPABILITY_NONE}, {@link
-     * ```
-     *     #IO_CAPABILITY_KBDISP} or more in {@link BluetoothAdapter}.
-     * ```
      */
-    fun setIoCapability(ioCapabilityClassic: Int, ioCapabilityBLE: Int) {
+    fun setIoCapability(ioCapabilityClassic: Int) {
         bluetoothAdapter.ioCapability = ioCapabilityClassic
-        bluetoothAdapter.leIoCapability = ioCapabilityBLE
 
         // Toggling airplane mode on/off to restart Bluetooth stack and reset the BLE.
         try {
@@ -273,4 +266,4 @@
         private const val TURN_AIRPLANE_MODE_OFF = 0
         private const val TURN_AIRPLANE_MODE_ON = 1
     }
-}
\ No newline at end of file
+}
diff --git a/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/Android.bp b/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/Android.bp
index 284d5c2..ec0392c 100644
--- a/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/Android.bp
+++ b/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/Android.bp
@@ -35,4 +35,5 @@
         // timeout in seconds.
         timeout: 36000,
     },
-}
\ No newline at end of file
+    upstream: true,
+}
diff --git a/nearby/tests/multidevices/host/Android.bp b/nearby/tests/multidevices/host/Android.bp
index b81032d..b6c1c9d 100644
--- a/nearby/tests/multidevices/host/Android.bp
+++ b/nearby/tests/multidevices/host/Android.bp
@@ -22,7 +22,10 @@
     name: "NearbyMultiDevicesTestSuite",
     main: "suite_main.py",
     srcs: ["*.py"],
-    libs: ["NearbyMultiDevicesHostHelper"],
+    libs: [
+        "NearbyMultiDevicesHostHelper",
+        "mobly",
+    ],
     test_suites: [
         "general-tests",
         "mts-tethering",
@@ -38,6 +41,11 @@
         // Package the JSON metadata with the Mobly test.
         "test_data/**/*",
     ],
+    version: {
+        py3: {
+            embedded_launcher: true,
+        },
+    },
 }
 
 python_library_host {
diff --git a/nearby/tests/multidevices/host/AndroidTest.xml b/nearby/tests/multidevices/host/AndroidTest.xml
index c1f6a70..fff0ed1 100644
--- a/nearby/tests/multidevices/host/AndroidTest.xml
+++ b/nearby/tests/multidevices/host/AndroidTest.xml
@@ -42,11 +42,6 @@
             <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
             <option name="run-command" value="wm dismiss-keyguard" />
         </target_preparer>
-        <target_preparer class="com.android.tradefed.targetprep.PythonVirtualenvPreparer">
-          <!-- Any python dependencies can be specified and will be installed with pip -->
-          <!-- TODO(b/225958696): Import python dependencies -->
-          <option name="dep-module" value="mobly" />
-        </target_preparer>
         <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
             <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
             <option name="screen-always-on" value="on" />
diff --git a/nearby/tests/multidevices/host/suite_main.py b/nearby/tests/multidevices/host/suite_main.py
index 4f5d48c..9a580fb 100644
--- a/nearby/tests/multidevices/host/suite_main.py
+++ b/nearby/tests/multidevices/host/suite_main.py
@@ -31,11 +31,9 @@
 ]
 
 
-def _valid_argument(arg: str) -> bool:
-    return arg.startswith(('--config', '-c', '--tests', '--test_case'))
-
-
 if __name__ == '__main__':
     logging.basicConfig(filename=_BOOTSTRAP_LOGGING_FILENAME, level=logging.INFO)
-    suite_runner.run_suite(argv=[arg for arg in sys.argv if _valid_argument(arg)],
-                           test_classes=_TEST_CLASSES_LIST)
+    if '--' in sys.argv:
+        index = sys.argv.index('--')
+        sys.argv = sys.argv[:1] + sys.argv[index + 1:]
+    suite_runner.run_suite(test_classes=_TEST_CLASSES_LIST)
diff --git a/nearby/tests/robotests/Android.bp b/nearby/tests/robotests/Android.bp
index 56c0107..70fa0c3 100644
--- a/nearby/tests/robotests/Android.bp
+++ b/nearby/tests/robotests/Android.bp
@@ -42,15 +42,14 @@
         "androidx.lifecycle_lifecycle-runtime",
         "androidx.mediarouter_mediarouter-nodeps",
         "error_prone_annotations",
-        "mockito-robolectric-prebuilt",
         "service-nearby-pre-jarjar",
         "truth-prebuilt",
         "robolectric_android-all-stub",
-        "Robolectric_all-target",
     ],
 
     test_options: {
         // timeout in seconds.
         timeout: 36000,
     },
+    upstream: true,
 }
diff --git a/netd/Android.bp b/netd/Android.bp
index c731b8b..473460d 100644
--- a/netd/Android.bp
+++ b/netd/Android.bp
@@ -63,6 +63,7 @@
     ],
     srcs: [
         "BpfHandlerTest.cpp",
+        "BpfBaseTest.cpp"
     ],
     static_libs: [
         "libnetd_updatable",
@@ -72,6 +73,7 @@
         "libcutils",
         "liblog",
         "libnetdutils",
+        "libprocessgroup",
     ],
     compile_multilib: "both",
     multilib: {
diff --git a/netd/BpfBaseTest.cpp b/netd/BpfBaseTest.cpp
new file mode 100644
index 0000000..c979a7b
--- /dev/null
+++ b/netd/BpfBaseTest.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <linux/inet_diag.h>
+#include <linux/sock_diag.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+#include <cutils/qtaguid.h>
+#include <processgroup/processgroup.h>
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <netdutils/NetNativeTestBase.h>
+
+#include "bpf/BpfMap.h"
+#include "bpf/BpfUtils.h"
+#include "netd.h"
+
+using android::base::Result;
+
+namespace android {
+namespace bpf {
+
+// Use the upper limit of uid to avoid conflict with real app uids. We can't use UID_MAX because
+// it's -1, which is INVALID_UID.
+constexpr uid_t TEST_UID = UID_MAX - 1;
+constexpr uint32_t TEST_TAG = 42;
+
+class BpfBasicTest : public NetNativeTestBase {
+  protected:
+    BpfBasicTest() {}
+};
+
+TEST_F(BpfBasicTest, TestCgroupMounted) {
+    std::string cg2_path;
+    ASSERT_EQ(true, CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &cg2_path));
+    ASSERT_EQ(0, access(cg2_path.c_str(), R_OK));
+    ASSERT_EQ(0, access((cg2_path + "/cgroup.controllers").c_str(), R_OK));
+}
+
+TEST_F(BpfBasicTest, TestTagSocket) {
+    BpfMap<uint64_t, UidTagValue> cookieTagMap(COOKIE_TAG_MAP_PATH);
+    ASSERT_TRUE(cookieTagMap.isValid());
+    int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
+    ASSERT_LE(0, sock);
+    uint64_t cookie = getSocketCookie(sock);
+    ASSERT_NE(NONEXISTENT_COOKIE, cookie);
+    ASSERT_EQ(0, qtaguid_tagSocket(sock, TEST_TAG, TEST_UID));
+    Result<UidTagValue> tagResult = cookieTagMap.readValue(cookie);
+    ASSERT_RESULT_OK(tagResult);
+    ASSERT_EQ(TEST_UID, tagResult.value().uid);
+    ASSERT_EQ(TEST_TAG, tagResult.value().tag);
+    ASSERT_EQ(0, qtaguid_untagSocket(sock));
+    tagResult = cookieTagMap.readValue(cookie);
+    ASSERT_FALSE(tagResult.ok());
+    ASSERT_EQ(ENOENT, tagResult.error().code());
+}
+
+TEST_F(BpfBasicTest, TestCloseSocketWithoutUntag) {
+    BpfMap<uint64_t, UidTagValue> cookieTagMap(COOKIE_TAG_MAP_PATH);
+    ASSERT_TRUE(cookieTagMap.isValid());
+    int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
+    ASSERT_LE(0, sock);
+    uint64_t cookie = getSocketCookie(sock);
+    ASSERT_NE(NONEXISTENT_COOKIE, cookie);
+    ASSERT_EQ(0, qtaguid_tagSocket(sock, TEST_TAG, TEST_UID));
+    Result<UidTagValue> tagResult = cookieTagMap.readValue(cookie);
+    ASSERT_RESULT_OK(tagResult);
+    ASSERT_EQ(TEST_UID, tagResult.value().uid);
+    ASSERT_EQ(TEST_TAG, tagResult.value().tag);
+    ASSERT_EQ(0, close(sock));
+    // Check map periodically until sk destroy handler have done its job.
+    for (int i = 0; i < 1000; i++) {
+        usleep(5000);  // 5ms
+        tagResult = cookieTagMap.readValue(cookie);
+        if (!tagResult.ok()) {
+            ASSERT_EQ(ENOENT, tagResult.error().code());
+            return;
+        }
+    }
+    FAIL() << "socket tag still exist after 5s";
+}
+
+}
+}
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 7950ff7..8081d12 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -32,7 +32,6 @@
 namespace net {
 
 using base::unique_fd;
-using bpf::NONEXISTENT_COOKIE;
 using bpf::getSocketCookie;
 using bpf::retrieveProgram;
 using netdutils::Status;
@@ -93,7 +92,7 @@
     // cgroup if the program is pinned properly.
     // TODO: delete the if statement once all devices should support cgroup
     // socket filter (ie. the minimum kernel version required is 4.14).
-    if (!access(CGROUP_SOCKET_PROG_PATH, F_OK)) {
+    if (bpf::isAtLeastKernelVersion(4, 14, 0)) {
         RETURN_IF_NOT_OK(
                 attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
     }
@@ -185,7 +184,7 @@
     }
 
     uint64_t sock_cookie = getSocketCookie(sockFd);
-    if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
+    if (!sock_cookie) return -errno;
 
     UidTagValue newKey = {.uid = (uint32_t)chargeUid, .tag = tag};
 
@@ -249,7 +248,7 @@
 
 int BpfHandler::untagSocket(int sockFd) {
     uint64_t sock_cookie = getSocketCookie(sockFd);
-    if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
+    if (!sock_cookie) return -errno;
 
     if (!mCookieTagMap.isValid()) return -EPERM;
     base::Result<void> res = mCookieTagMap.deleteValue(sock_cookie);
diff --git a/netd/BpfHandler.h b/netd/BpfHandler.h
index 925a725..a6da4eb 100644
--- a/netd/BpfHandler.h
+++ b/netd/BpfHandler.h
@@ -18,7 +18,7 @@
 
 #include <netdutils/Status.h>
 #include "bpf/BpfMap.h"
-#include "bpf_shared.h"
+#include "netd.h"
 
 using android::bpf::BpfMap;
 using android::bpf::BpfMapRO;
diff --git a/service-t/Android.bp b/service-t/Android.bp
index 2e7a4f3..7de749c 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -50,7 +50,9 @@
         "framework-configinfrastructure",
         "framework-connectivity-pre-jarjar",
         "framework-connectivity-t-pre-jarjar",
-        "framework-tethering",
+        // TODO: use framework-tethering-pre-jarjar when it is separated from framework-tethering
+        "framework-tethering.impl",
+        "framework-wifi",
         "service-connectivity-pre-jarjar",
         "service-nearby-pre-jarjar",
         "ServiceConnectivityResources",
@@ -73,3 +75,47 @@
         "//packages/modules/IPsec/tests/iketests",
     ],
 }
+
+// Test building mDNS as a standalone, so that it can be imported into other repositories as-is.
+// The mDNS code is platform code so it should use framework-annotations-lib, contrary to apps that
+// should use sdk_version: "system_current" and only androidx.annotation_annotation. But this build
+// rule verifies that the mDNS code can be built into apps, if code transformations are applied to
+// the annotations.
+// When using "system_current", framework annotations are not available; they would appear as
+// package-private as they are marked as such in the system_current stubs. So build against
+// core_platform and add the stubs manually in "libs". See http://b/147773144#comment7.
+java_library {
+    name: "service-connectivity-mdns-standalone-build-test",
+    sdk_version: "core_platform",
+    srcs: [
+        ":service-mdns-droidstubs",
+        "src/com/android/server/connectivity/mdns/**/*.java",
+    ],
+    exclude_srcs: [
+        "src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java",
+        "src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java"
+    ],
+    static_libs: [
+        "net-utils-device-common-mdns-standalone-build-test",
+    ],
+    libs: [
+        "framework-annotations-lib",
+        "android_system_stubs_current",
+        "androidx.annotation_annotation",
+    ],
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+droidstubs {
+    name: "service-mdns-droidstubs",
+    srcs: ["src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java"],
+    libs: [
+        "net-utils-device-common-mdns-standalone-build-test",
+        "service-connectivity-tiramisu-pre-jarjar"
+    ],
+    visibility: [
+        "//visibility:private",
+    ],
+}
\ No newline at end of file
diff --git a/service-t/jni/com_android_server_net_NetworkStatsFactory.cpp b/service-t/jni/com_android_server_net_NetworkStatsFactory.cpp
index a3299a7..a16757b 100644
--- a/service-t/jni/com_android_server_net_NetworkStatsFactory.cpp
+++ b/service-t/jni/com_android_server_net_NetworkStatsFactory.cpp
@@ -170,23 +170,10 @@
     return 0;
 }
 
-static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, jint limitUid,
-        jobjectArray limitIfacesObj, jint limitTag) {
-
-    std::vector<std::string> limitIfaces;
-    if (limitIfacesObj != NULL && env->GetArrayLength(limitIfacesObj) > 0) {
-        int num = env->GetArrayLength(limitIfacesObj);
-        for (int i = 0; i < num; i++) {
-            jstring string = (jstring)env->GetObjectArrayElement(limitIfacesObj, i);
-            ScopedUtfChars string8(env, string);
-            if (string8.c_str() != NULL) {
-                limitIfaces.push_back(std::string(string8.c_str()));
-            }
-        }
-    }
+static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats) {
     std::vector<stats_line> lines;
 
-    if (parseBpfNetworkStatsDetail(&lines, limitIfaces, limitTag, limitUid) < 0)
+    if (parseBpfNetworkStatsDetail(&lines) < 0)
         return -1;
 
     return statsLinesToNetworkStats(env, clazz, stats, lines);
@@ -202,15 +189,15 @@
 }
 
 static const JNINativeMethod gMethods[] = {
-        { "nativeReadNetworkStatsDetail",
-                "(Landroid/net/NetworkStats;I[Ljava/lang/String;I)I",
+        { "nativeReadNetworkStatsDetail", "(Landroid/net/NetworkStats;)I",
                 (void*) readNetworkStatsDetail },
         { "nativeReadNetworkStatsDev", "(Landroid/net/NetworkStats;)I",
                 (void*) readNetworkStatsDev },
 };
 
 int register_android_server_net_NetworkStatsFactory(JNIEnv* env) {
-    int err = jniRegisterNativeMethods(env, "com/android/server/net/NetworkStatsFactory", gMethods,
+    int err = jniRegisterNativeMethods(env,
+            "android/net/connectivity/com/android/server/net/NetworkStatsFactory", gMethods,
             NELEM(gMethods));
     gStringClass = env->FindClass("java/lang/String");
     gStringClass = static_cast<jclass>(env->NewGlobalRef(gStringClass));
diff --git a/service-t/jni/com_android_server_net_NetworkStatsService.cpp b/service-t/jni/com_android_server_net_NetworkStatsService.cpp
index 39cbaf7..dab9d07 100644
--- a/service-t/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/service-t/jni/com_android_server_net_NetworkStatsService.cpp
@@ -30,9 +30,11 @@
 
 #include "bpf/BpfUtils.h"
 #include "netdbpf/BpfNetworkStats.h"
+#include "netdbpf/NetworkTraceHandler.h"
 
 using android::bpf::bpfGetUidStats;
 using android::bpf::bpfGetIfaceStats;
+using android::bpf::NetworkTraceHandler;
 
 namespace android {
 
@@ -67,7 +69,7 @@
     }
 }
 
-static jlong getTotalStat(JNIEnv* env, jclass clazz, jint type) {
+static jlong nativeGetTotalStat(JNIEnv* env, jclass clazz, jint type) {
     Stats stats = {};
 
     if (bpfGetIfaceStats(NULL, &stats) == 0) {
@@ -77,7 +79,7 @@
     }
 }
 
-static jlong getIfaceStat(JNIEnv* env, jclass clazz, jstring iface, jint type) {
+static jlong nativeGetIfaceStat(JNIEnv* env, jclass clazz, jstring iface, jint type) {
     ScopedUtfChars iface8(env, iface);
     if (iface8.c_str() == NULL) {
         return UNKNOWN;
@@ -92,7 +94,7 @@
     }
 }
 
-static jlong getUidStat(JNIEnv* env, jclass clazz, jint uid, jint type) {
+static jlong nativeGetUidStat(JNIEnv* env, jclass clazz, jint uid, jint type) {
     Stats stats = {};
 
     if (bpfGetUidStats(uid, &stats) == 0) {
@@ -102,15 +104,21 @@
     }
 }
 
+static void nativeInitNetworkTracing(JNIEnv* env, jclass clazz) {
+    NetworkTraceHandler::InitPerfettoTracing();
+}
+
 static const JNINativeMethod gMethods[] = {
-        {"nativeGetTotalStat", "(I)J", (void*)getTotalStat},
-        {"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*)getIfaceStat},
-        {"nativeGetUidStat", "(II)J", (void*)getUidStat},
+        {"nativeGetTotalStat", "(I)J", (void*)nativeGetTotalStat},
+        {"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*)nativeGetIfaceStat},
+        {"nativeGetUidStat", "(II)J", (void*)nativeGetUidStat},
+        {"nativeInitNetworkTracing", "()V", (void*)nativeInitNetworkTracing},
 };
 
 int register_android_server_net_NetworkStatsService(JNIEnv* env) {
-    return jniRegisterNativeMethods(env, "com/android/server/net/NetworkStatsService", gMethods,
-                                    NELEM(gMethods));
+    return jniRegisterNativeMethods(env,
+            "android/net/connectivity/com/android/server/net/NetworkStatsService", gMethods,
+            NELEM(gMethods));
 }
 
 }
diff --git a/service-t/native/libs/libnetworkstats/Android.bp b/service-t/native/libs/libnetworkstats/Android.bp
index 5b3d314..0dfd0af 100644
--- a/service-t/native/libs/libnetworkstats/Android.bp
+++ b/service-t/native/libs/libnetworkstats/Android.bp
@@ -24,12 +24,21 @@
     host_supported: false,
     header_libs: ["bpf_connectivity_headers"],
     srcs: [
-        "BpfNetworkStats.cpp"
+        "BpfNetworkStats.cpp",
+        "NetworkTraceHandler.cpp",
+        "NetworkTracePoller.cpp",
     ],
     shared_libs: [
         "libbase",
+        "libcutils",
         "liblog",
     ],
+    static_libs: [
+        "libperfetto_client_experimental",
+    ],
+    export_static_lib_headers: [
+        "libperfetto_client_experimental",
+    ],
     export_include_dirs: ["include"],
     cflags: [
         "-Wall",
@@ -54,6 +63,8 @@
     header_libs: ["bpf_connectivity_headers"],
     srcs: [
         "BpfNetworkStatsTest.cpp",
+        "NetworkTraceHandlerTest.cpp",
+        "NetworkTracePollerTest.cpp",
     ],
     cflags: [
         "-Wall",
@@ -64,10 +75,15 @@
     static_libs: [
         "libgmock",
         "libnetworkstats",
+        "libperfetto_client_experimental",
+        "libprotobuf-cpp-lite",
+        "perfetto_trace_protos",
     ],
     shared_libs: [
         "libbase",
         "liblog",
+        "libcutils",
+        "libandroid_net",
     ],
     compile_multilib: "both",
     multilib: {
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
index 28de881..1bc8ca5 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
@@ -26,7 +26,7 @@
 #include "android-base/strings.h"
 #include "android-base/unique_fd.h"
 #include "bpf/BpfMap.h"
-#include "bpf_shared.h"
+#include "netd.h"
 #include "netdbpf/BpfNetworkStats.h"
 
 #ifdef LOG_TAG
@@ -109,13 +109,12 @@
     return newLine;
 }
 
-int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>* lines,
-                                       const std::vector<std::string>& limitIfaces, int limitTag,
-                                       int limitUid, const BpfMap<StatsKey, StatsValue>& statsMap,
+int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>& lines,
+                                       const BpfMap<StatsKey, StatsValue>& statsMap,
                                        const BpfMap<uint32_t, IfaceValue>& ifaceMap) {
     int64_t unknownIfaceBytesTotal = 0;
     const auto processDetailUidStats =
-            [lines, &limitIfaces, &limitTag, &limitUid, &unknownIfaceBytesTotal, &ifaceMap](
+            [&lines, &unknownIfaceBytesTotal, &ifaceMap](
                     const StatsKey& key,
                     const BpfMap<StatsKey, StatsValue>& statsMap) -> Result<void> {
         char ifname[IFNAMSIZ];
@@ -123,23 +122,17 @@
                                 &unknownIfaceBytesTotal)) {
             return Result<void>();
         }
-        std::string ifnameStr(ifname);
-        if (limitIfaces.size() > 0 &&
-            std::find(limitIfaces.begin(), limitIfaces.end(), ifnameStr) == limitIfaces.end()) {
-            // Nothing matched; skip this line.
-            return Result<void>();
-        }
-        if (limitTag != TAG_ALL && uint32_t(limitTag) != key.tag) {
-            return Result<void>();
-        }
-        if (limitUid != UID_ALL && uint32_t(limitUid) != key.uid) {
-            return Result<void>();
-        }
         Result<StatsValue> statsEntry = statsMap.readValue(key);
         if (!statsEntry.ok()) {
             return base::ResultError(statsEntry.error().message(), statsEntry.error().code());
         }
-        lines->push_back(populateStatsEntry(key, statsEntry.value(), ifname));
+        stats_line newLine = populateStatsEntry(key, statsEntry.value(), ifname);
+        lines.push_back(newLine);
+        if (newLine.tag) {
+            // account tagged traffic in the untagged stats (for historical reasons?)
+            newLine.tag = 0;
+            lines.push_back(newLine);
+        }
         return Result<void>();
     };
     Result<void> res = statsMap.iterate(processDetailUidStats);
@@ -162,9 +155,7 @@
     return 0;
 }
 
-int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines,
-                               const std::vector<std::string>& limitIfaces, int limitTag,
-                               int limitUid) {
+int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines) {
     static BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
     static BpfMapRO<uint32_t, uint32_t> configurationMap(CONFIGURATION_MAP_PATH);
     static BpfMap<StatsKey, StatsValue> statsMapA(STATS_MAP_A_PATH);
@@ -195,8 +186,7 @@
     // TODO: the above comment feels like it may be obsolete / out of date,
     // since we no longer swap the map via netd binder rpc - though we do
     // still swap it.
-    int ret = parseBpfNetworkStatsDetailInternal(lines, limitIfaces, limitTag, limitUid,
-                                                 *inactiveStatsMap, ifaceIndexNameMap);
+    int ret = parseBpfNetworkStatsDetailInternal(*lines, *inactiveStatsMap, ifaceIndexNameMap);
     if (ret) {
         ALOGE("parse detail network stats failed: %s", strerror(errno));
         return ret;
@@ -211,11 +201,11 @@
     return 0;
 }
 
-int parseBpfNetworkStatsDevInternal(std::vector<stats_line>* lines,
+int parseBpfNetworkStatsDevInternal(std::vector<stats_line>& lines,
                                     const BpfMap<uint32_t, StatsValue>& statsMap,
                                     const BpfMap<uint32_t, IfaceValue>& ifaceMap) {
     int64_t unknownIfaceBytesTotal = 0;
-    const auto processDetailIfaceStats = [lines, &unknownIfaceBytesTotal, &ifaceMap, &statsMap](
+    const auto processDetailIfaceStats = [&lines, &unknownIfaceBytesTotal, &ifaceMap, &statsMap](
                                              const uint32_t& key, const StatsValue& value,
                                              const BpfMap<uint32_t, StatsValue>&) {
         char ifname[IFNAMSIZ];
@@ -227,7 +217,7 @@
                 .tag = (uint32_t)TAG_NONE,
                 .counterSet = (uint32_t)SET_ALL,
         };
-        lines->push_back(populateStatsEntry(fakeKey, value, ifname));
+        lines.push_back(populateStatsEntry(fakeKey, value, ifname));
         return Result<void>();
     };
     Result<void> res = statsMap.iterateWithValue(processDetailIfaceStats);
@@ -244,33 +234,28 @@
 int parseBpfNetworkStatsDev(std::vector<stats_line>* lines) {
     static BpfMapRO<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
     static BpfMapRO<uint32_t, StatsValue> ifaceStatsMap(IFACE_STATS_MAP_PATH);
-    return parseBpfNetworkStatsDevInternal(lines, ifaceStatsMap, ifaceIndexNameMap);
+    return parseBpfNetworkStatsDevInternal(*lines, ifaceStatsMap, ifaceIndexNameMap);
 }
 
-uint64_t combineUidTag(const uid_t uid, const uint32_t tag) {
-    return (uint64_t)uid << 32 | tag;
-}
-
-void groupNetworkStats(std::vector<stats_line>* lines) {
-    if (lines->size() <= 1) return;
-    std::sort(lines->begin(), lines->end());
+void groupNetworkStats(std::vector<stats_line>& lines) {
+    if (lines.size() <= 1) return;
+    std::sort(lines.begin(), lines.end());
 
     // Similar to std::unique(), but aggregates the duplicates rather than discarding them.
-    size_t nextOutput = 0;
-    for (size_t i = 1; i < lines->size(); i++) {
-        if (lines->at(nextOutput) == lines->at(i)) {
-            lines->at(nextOutput) += lines->at(i);
+    size_t currentOutput = 0;
+    for (size_t i = 1; i < lines.size(); i++) {
+        // note that == operator only compares the 'key' portion: iface/uid/tag/set
+        if (lines[currentOutput] == lines[i]) {
+            // while += operator only affects the 'data' portion: {rx,tx}{Bytes,Packets}
+            lines[currentOutput] += lines[i];
         } else {
-            nextOutput++;
-            if (nextOutput != i) {
-                lines->at(nextOutput) = lines->at(i);
-            }
+            // okay, we're done aggregating the current line, move to the next one
+            lines[++currentOutput] = lines[i];
         }
     }
 
-    if (lines->size() != nextOutput + 1) {
-        lines->resize(nextOutput + 1);
-    }
+    // possibly shrink the vector - currentOutput is the last line with valid data
+    lines.resize(currentOutput + 1);
 }
 
 // True if lhs equals to rhs, only compare iface, uid, tag and set.
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
index 6f9c8c2..ccd3f5e 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
@@ -225,18 +225,11 @@
     ASSERT_EQ(0, bpfGetUidStatsInternal(TEST_UID2, &result2, mFakeAppUidStatsMap));
     expectStatsEqual(value2, result2);
     std::vector<stats_line> lines;
-    std::vector<std::string> ifaces;
     populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
     populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET1, value1, mFakeStatsMap);
     populateFakeStats(TEST_UID2, 0, IFACE_INDEX3, TEST_COUNTERSET1, value1, mFakeStatsMap);
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
-                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
-    ASSERT_EQ((unsigned long)2, lines.size());
-    lines.clear();
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID2,
-                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
-    ASSERT_EQ((unsigned long)1, lines.size());
-    expectStatsLineEqual(value1, IFACE_NAME3, TEST_UID2, TEST_COUNTERSET1, 0, lines.front());
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ((unsigned long)3, lines.size());
 }
 
 TEST_F(BpfNetworkStatsHelperTest, TestGetIfaceStatsInternal) {
@@ -297,24 +290,8 @@
                       mFakeStatsMap);
     populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
     std::vector<stats_line> lines;
-    std::vector<std::string> ifaces;
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
-                                                    mFakeIfaceIndexNameMap));
-    ASSERT_EQ((unsigned long)4, lines.size());
-    lines.clear();
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
-                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
-    ASSERT_EQ((unsigned long)3, lines.size());
-    lines.clear();
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TEST_TAG, TEST_UID1,
-                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
-    ASSERT_EQ((unsigned long)2, lines.size());
-    lines.clear();
-    ifaces.push_back(std::string(IFACE_NAME1));
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TEST_TAG, TEST_UID1,
-                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
-    ASSERT_EQ((unsigned long)1, lines.size());
-    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines.front());
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ((unsigned long)7, lines.size());
 }
 
 TEST_F(BpfNetworkStatsHelperTest, TestGetStatsWithSkippedIface) {
@@ -326,30 +303,15 @@
             .txPackets = TEST_PACKET1,
             .txBytes = TEST_BYTES1,
     };
-    populateFakeStats(0, 0, 0, OVERFLOW_COUNTERSET, value1, mFakeStatsMap);
+    // next stats entry will be ignored due to ifindex 0 not being present in mFakeIfaceIndexNameMap
+    populateFakeStats(0, 0, 0, TEST_COUNTERSET1, value1, mFakeStatsMap);
     populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
     populateFakeStats(TEST_UID1, 0, IFACE_INDEX2, TEST_COUNTERSET0, value1, mFakeStatsMap);
     populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET1, value1, mFakeStatsMap);
     populateFakeStats(TEST_UID2, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
     std::vector<stats_line> lines;
-    std::vector<std::string> ifaces;
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
-                                                    mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)4, lines.size());
-    lines.clear();
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
-                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
-    ASSERT_EQ((unsigned long)3, lines.size());
-    lines.clear();
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID2,
-                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
-    ASSERT_EQ((unsigned long)1, lines.size());
-    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID2, TEST_COUNTERSET0, 0, lines.front());
-    lines.clear();
-    ifaces.push_back(std::string(IFACE_NAME1));
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, TEST_UID1,
-                                                    mFakeStatsMap, mFakeIfaceIndexNameMap));
-    ASSERT_EQ((unsigned long)2, lines.size());
 }
 
 TEST_F(BpfNetworkStatsHelperTest, TestUnknownIfaceError) {
@@ -386,10 +348,8 @@
                                            ifname, curKey, &unknownIfaceBytesTotal));
     ASSERT_EQ(-1, unknownIfaceBytesTotal);
     std::vector<stats_line> lines;
-    std::vector<std::string> ifaces;
     // TODO: find a way to test the total of unknown Iface Bytes go above limit.
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
-                                                    mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)1, lines.size());
     expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines.front());
 }
@@ -421,7 +381,7 @@
     EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value2, BPF_ANY));
     std::vector<stats_line> lines;
     ASSERT_EQ(0,
-              parseBpfNetworkStatsDevInternal(&lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap));
+              parseBpfNetworkStatsDevInternal(lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((unsigned long)4, lines.size());
 
     expectStatsLineEqual(value1, IFACE_NAME1, UID_ALL, SET_ALL, TAG_NONE, lines[0]);
@@ -449,28 +409,32 @@
             .txPackets = TEST_PACKET0,
             .txBytes = TEST_BYTES0,
     };
-    StatsValue value3 = {
+    StatsValue value3 = {  // value1 *2
             .rxPackets = TEST_PACKET0 * 2,
             .rxBytes = TEST_BYTES0 * 2,
             .txPackets = TEST_PACKET1 * 2,
             .txBytes = TEST_BYTES1 * 2,
     };
+    StatsValue value5 = {  // value2 + value3
+            .rxPackets = TEST_PACKET1 + TEST_PACKET0 * 2,
+            .rxBytes = TEST_BYTES1 + TEST_BYTES0 * 2,
+            .txPackets = TEST_PACKET0 + TEST_PACKET1 * 2,
+            .txBytes = TEST_BYTES0 + TEST_BYTES1 * 2,
+    };
 
     std::vector<stats_line> lines;
-    std::vector<std::string> ifaces;
 
     // Test empty stats.
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
-                                                    mFakeIfaceIndexNameMap));
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((size_t) 0, lines.size());
     lines.clear();
 
     // Test 1 line stats.
     populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
-                                                    mFakeIfaceIndexNameMap));
-    ASSERT_EQ((size_t) 1, lines.size());
-    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[0]);
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ((size_t) 2, lines.size());  // TEST_TAG != 0 -> 1 entry becomes 2 lines
+    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines[0]);
+    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[1]);
     lines.clear();
 
     // These items should not be grouped.
@@ -479,25 +443,27 @@
     populateFakeStats(TEST_UID1, TEST_TAG + 1, IFACE_INDEX1, TEST_COUNTERSET0, value2,
                       mFakeStatsMap);
     populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
-                                                    mFakeIfaceIndexNameMap));
-    ASSERT_EQ((size_t) 5, lines.size());
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ((size_t) 9, lines.size());
     lines.clear();
 
     // These items should be grouped.
     populateFakeStats(TEST_UID1, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap);
     populateFakeStats(TEST_UID2, TEST_TAG, IFACE_INDEX3, TEST_COUNTERSET0, value1, mFakeStatsMap);
 
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
-                                                    mFakeIfaceIndexNameMap));
-    ASSERT_EQ((size_t) 5, lines.size());
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ((size_t) 9, lines.size());
 
     // Verify Sorted & Grouped.
-    expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[0]);
-    expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET1, TEST_TAG, lines[1]);
-    expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG + 1, lines[2]);
-    expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID2, TEST_COUNTERSET0, TEST_TAG, lines[3]);
-    expectStatsLineEqual(value2, IFACE_NAME2, TEST_UID1, TEST_COUNTERSET0, TEST_TAG, lines[4]);
+    expectStatsLineEqual(value5, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0,            lines[0]);
+    expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET1, 0,            lines[1]);
+    expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG,     lines[2]);
+    expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET1, TEST_TAG,     lines[3]);
+    expectStatsLineEqual(value2, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, TEST_TAG + 1, lines[4]);
+    expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID2, TEST_COUNTERSET0, 0,            lines[5]);
+    expectStatsLineEqual(value3, IFACE_NAME1, TEST_UID2, TEST_COUNTERSET0, TEST_TAG,     lines[6]);
+    expectStatsLineEqual(value2, IFACE_NAME2, TEST_UID1, TEST_COUNTERSET0, 0,            lines[7]);
+    expectStatsLineEqual(value2, IFACE_NAME2, TEST_UID1, TEST_COUNTERSET0, TEST_TAG,     lines[8]);
     lines.clear();
 
     // Perform test on IfaceStats.
@@ -511,7 +477,7 @@
     EXPECT_RESULT_OK(mFakeIfaceStatsMap.writeValue(ifaceStatsKey, value1, BPF_ANY));
 
     ASSERT_EQ(0,
-              parseBpfNetworkStatsDevInternal(&lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap));
+              parseBpfNetworkStatsDevInternal(lines, mFakeIfaceStatsMap, mFakeIfaceIndexNameMap));
     ASSERT_EQ((size_t) 2, lines.size());
 
     expectStatsLineEqual(value3, IFACE_NAME1, UID_ALL, SET_ALL, TAG_NONE, lines[0]);
@@ -530,41 +496,48 @@
             .txPackets = TEST_PACKET1,
             .txBytes = TEST_BYTES1,
     };
+    StatsValue value4 = {  // value1 * 4
+            .rxPackets = TEST_PACKET0 * 4,
+            .rxBytes = TEST_BYTES0 * 4,
+            .txPackets = TEST_PACKET1 * 4,
+            .txBytes = TEST_BYTES1 * 4,
+    };
 
     // Mutate uid, 0 < TEST_UID1 < INT_MAX < INT_MIN < UINT_MAX.
-    populateFakeStats(0, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
-    populateFakeStats(UINT_MAX, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
-    populateFakeStats(INT_MIN, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
-    populateFakeStats(INT_MAX, TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(0,         TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(UINT_MAX,  TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(INT_MIN,   TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(INT_MAX,   TEST_TAG, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
 
     // Mutate tag, 0 < TEST_TAG < INT_MAX < INT_MIN < UINT_MAX.
-    populateFakeStats(TEST_UID1, INT_MAX, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
-    populateFakeStats(TEST_UID1, INT_MIN, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
-    populateFakeStats(TEST_UID1, 0, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(TEST_UID1, INT_MAX,  IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(TEST_UID1, INT_MIN,  IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
+    populateFakeStats(TEST_UID1, 0,        IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
     populateFakeStats(TEST_UID1, UINT_MAX, IFACE_INDEX1, TEST_COUNTERSET0, value1, mFakeStatsMap);
 
     // TODO: Mutate counterSet and enlarge TEST_MAP_SIZE if overflow on counterSet is possible.
 
     std::vector<stats_line> lines;
-    std::vector<std::string> ifaces;
-    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(&lines, ifaces, TAG_ALL, UID_ALL, mFakeStatsMap,
-                                                    mFakeIfaceIndexNameMap));
-    ASSERT_EQ((size_t) 8, lines.size());
+    ASSERT_EQ(0, parseBpfNetworkStatsDetailInternal(lines, mFakeStatsMap, mFakeIfaceIndexNameMap));
+    ASSERT_EQ((size_t) 12, lines.size());
 
     // Uid 0 first
-    expectStatsLineEqual(value1, IFACE_NAME1, 0, TEST_COUNTERSET0, TEST_TAG, lines[0]);
+    expectStatsLineEqual(value1, IFACE_NAME1, 0,         TEST_COUNTERSET0, 0,        lines[0]);
+    expectStatsLineEqual(value1, IFACE_NAME1, 0,         TEST_COUNTERSET0, TEST_TAG, lines[1]);
 
     // Test uid, mutate tag.
-    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0, lines[1]);
-    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, INT_MAX, lines[2]);
-    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, INT_MIN, lines[3]);
-    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, UINT_MAX, lines[4]);
+    expectStatsLineEqual(value4, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, 0,        lines[2]);
+    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, INT_MAX,  lines[3]);
+    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, INT_MIN,  lines[4]);
+    expectStatsLineEqual(value1, IFACE_NAME1, TEST_UID1, TEST_COUNTERSET0, UINT_MAX, lines[5]);
 
     // Mutate uid.
-    expectStatsLineEqual(value1, IFACE_NAME1, INT_MAX, TEST_COUNTERSET0, TEST_TAG, lines[5]);
-    expectStatsLineEqual(value1, IFACE_NAME1, INT_MIN, TEST_COUNTERSET0, TEST_TAG, lines[6]);
-    expectStatsLineEqual(value1, IFACE_NAME1, UINT_MAX, TEST_COUNTERSET0, TEST_TAG, lines[7]);
-    lines.clear();
+    expectStatsLineEqual(value1, IFACE_NAME1, INT_MAX,   TEST_COUNTERSET0, 0,        lines[6]);
+    expectStatsLineEqual(value1, IFACE_NAME1, INT_MAX,   TEST_COUNTERSET0, TEST_TAG, lines[7]);
+    expectStatsLineEqual(value1, IFACE_NAME1, INT_MIN,   TEST_COUNTERSET0, 0,        lines[8]);
+    expectStatsLineEqual(value1, IFACE_NAME1, INT_MIN,   TEST_COUNTERSET0, TEST_TAG, lines[9]);
+    expectStatsLineEqual(value1, IFACE_NAME1, UINT_MAX,  TEST_COUNTERSET0, 0,        lines[10]);
+    expectStatsLineEqual(value1, IFACE_NAME1, UINT_MAX,  TEST_COUNTERSET0, TEST_TAG, lines[11]);
 }
 }  // namespace bpf
 }  // namespace android
diff --git a/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp b/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp
new file mode 100644
index 0000000..c5f9631
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "NetworkTrace"
+
+#include "netdbpf/NetworkTraceHandler.h"
+
+#include <arpa/inet.h>
+#include <bpf/BpfUtils.h>
+#include <log/log.h>
+#include <perfetto/config/android/network_trace_config.pbzero.h>
+#include <perfetto/trace/android/network_trace.pbzero.h>
+#include <perfetto/trace/profiling/profile_packet.pbzero.h>
+#include <perfetto/tracing/platform.h>
+#include <perfetto/tracing/tracing.h>
+
+// Note: this is initializing state for a templated Perfetto type that resides
+// in the `perfetto` namespace. This must be defined in the global scope.
+PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(android::bpf::NetworkTraceHandler);
+
+namespace android {
+namespace bpf {
+using ::android::bpf::internal::NetworkTracePoller;
+using ::perfetto::protos::pbzero::NetworkPacketBundle;
+using ::perfetto::protos::pbzero::NetworkPacketEvent;
+using ::perfetto::protos::pbzero::NetworkPacketTraceConfig;
+using ::perfetto::protos::pbzero::TracePacket;
+using ::perfetto::protos::pbzero::TrafficDirection;
+
+// Bundling takes groups of packets with similar contextual fields (generally,
+// all fields except timestamp and length) and summarises them in a single trace
+// packet. For example, rather than
+//
+//   {.timestampNs = 1, .uid = 1000, .tag = 123, .len = 72}
+//   {.timestampNs = 2, .uid = 1000, .tag = 123, .len = 100}
+//   {.timestampNs = 5, .uid = 1000, .tag = 123, .len = 456}
+//
+// The output will be something like
+//   {
+//     .timestamp = 1
+//     .ctx = {.uid = 1000, .tag = 123}
+//     .timestamp = [0, 1, 4], // delta encoded
+//     .length = [72, 100, 456], // should be zipped with timestamps
+//   }
+//
+// Most workloads have many packets from few contexts. Bundling greatly reduces
+// the amount of redundant information written, thus reducing the overall trace
+// size. Interning ids are similarly based on unique bundle contexts.
+
+// Based on boost::hash_combine
+template <typename T, typename... Rest>
+void HashCombine(std::size_t& seed, const T& val, const Rest&... rest) {
+  seed ^= std::hash<T>()(val) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
+  (HashCombine(seed, rest), ...);
+}
+
+// Details summarises the timestamp and lengths of packets in a bundle.
+struct BundleDetails {
+  std::vector<std::pair<uint64_t, uint32_t>> time_and_len;
+  uint64_t minTs = std::numeric_limits<uint64_t>::max();
+  uint64_t maxTs = std::numeric_limits<uint64_t>::min();
+  uint32_t bytes = 0;
+};
+
+#define AGG_FIELDS(x)                                              \
+  (x).ifindex, (x).uid, (x).tag, (x).sport, (x).dport, (x).egress, \
+      (x).ipProto, (x).tcpFlags
+
+std::size_t BundleHash::operator()(const BundleKey& a) const {
+  std::size_t seed = 0;
+  HashCombine(seed, AGG_FIELDS(a));
+  return seed;
+}
+
+bool BundleEq::operator()(const BundleKey& a, const BundleKey& b) const {
+  return std::tie(AGG_FIELDS(a)) == std::tie(AGG_FIELDS(b));
+}
+
+// static
+void NetworkTraceHandler::RegisterDataSource() {
+  ALOGD("Registering Perfetto data source");
+  perfetto::DataSourceDescriptor dsd;
+  dsd.set_name("android.network_packets");
+  NetworkTraceHandler::Register(dsd);
+}
+
+// static
+void NetworkTraceHandler::InitPerfettoTracing() {
+  perfetto::TracingInitArgs args = {};
+  args.backends |= perfetto::kSystemBackend;
+  // The following line disables the Perfetto system consumer. Perfetto inlines
+  // the call to `Initialize` which allows the compiler to see that the branch
+  // with the SystemConsumerTracingBackend is not used. With LTO enabled, this
+  // strips the Perfetto consumer code and reduces the size of this binary by
+  // around 270KB total. Be careful when changing this value.
+  args.enable_system_consumer = false;
+  perfetto::Tracing::Initialize(args);
+  NetworkTraceHandler::RegisterDataSource();
+}
+
+// static
+NetworkTracePoller NetworkTraceHandler::sPoller(
+    [](const std::vector<PacketTrace>& packets) {
+      // Trace calls the provided callback for each active session. The context
+      // gets a reference to the NetworkTraceHandler instance associated with
+      // the session and delegates writing. The corresponding handler will write
+      // with the setting specified in the trace config.
+      NetworkTraceHandler::Trace([&](NetworkTraceHandler::TraceContext ctx) {
+        ctx.GetDataSourceLocked()->Write(packets, ctx);
+      });
+    });
+
+void NetworkTraceHandler::OnSetup(const SetupArgs& args) {
+  const std::string& raw = args.config->network_packet_trace_config_raw();
+  NetworkPacketTraceConfig::Decoder config(raw);
+
+  mPollMs = config.poll_ms();
+  if (mPollMs < 100) {
+    ALOGI("poll_ms is missing or below the 100ms minimum. Increasing to 100ms");
+    mPollMs = 100;
+  }
+
+  mInternLimit = config.intern_limit();
+  mAggregationThreshold = config.aggregation_threshold();
+  mDropLocalPort = config.drop_local_port();
+  mDropRemotePort = config.drop_remote_port();
+  mDropTcpFlags = config.drop_tcp_flags();
+}
+
+void NetworkTraceHandler::OnStart(const StartArgs&) {
+  if (mIsTest) return;  // Don't touch non-hermetic bpf in test.
+  mStarted = sPoller.Start(mPollMs);
+}
+
+void NetworkTraceHandler::OnStop(const StopArgs&) {
+  if (mIsTest) return;  // Don't touch non-hermetic bpf in test.
+  if (mStarted) sPoller.Stop();
+  mStarted = false;
+
+  // Although this shouldn't be required, there seems to be some cases when we
+  // don't fill enough of a Perfetto Chunk for Perfetto to automatically commit
+  // the traced data. This manually flushes OnStop so we commit at least once.
+  NetworkTraceHandler::Trace([&](NetworkTraceHandler::TraceContext ctx) {
+    perfetto::LockedHandle<NetworkTraceHandler> handle =
+        ctx.GetDataSourceLocked();
+    // Trace is called for all active handlers, only flush our context. Since
+    // handle doesn't have a `.get()`, use `*` and `&` to get what it points to.
+    if (&(*handle) != this) return;
+    ctx.Flush();
+  });
+}
+
+void NetworkTraceHandler::Write(const std::vector<PacketTrace>& packets,
+                                NetworkTraceHandler::TraceContext& ctx) {
+  // TODO: remove this fallback once Perfetto stable has support for bundles.
+  if (!mInternLimit && !mAggregationThreshold) {
+    for (const PacketTrace& pkt : packets) {
+      auto dst = ctx.NewTracePacket();
+      dst->set_timestamp(pkt.timestampNs);
+      auto* event = dst->set_network_packet();
+      event->set_length(pkt.length);
+      Fill(pkt, event);
+    }
+    return;
+  }
+
+  uint64_t minTs = std::numeric_limits<uint64_t>::max();
+  std::unordered_map<BundleKey, BundleDetails, BundleHash, BundleEq> bundles;
+  for (const PacketTrace& pkt : packets) {
+    BundleKey key = pkt;
+
+    // Dropping fields should remove them from the output and remove them from
+    // the aggregation key. In order to do the latter without changing the hash
+    // function, set the dropped fields to zero.
+    if (mDropTcpFlags) key.tcpFlags = 0;
+    if (mDropLocalPort) (key.egress ? key.sport : key.dport) = 0;
+    if (mDropRemotePort) (key.egress ? key.dport : key.sport) = 0;
+
+    minTs = std::min(minTs, pkt.timestampNs);
+
+    BundleDetails& bundle = bundles[key];
+    bundle.time_and_len.emplace_back(pkt.timestampNs, pkt.length);
+    bundle.minTs = std::min(bundle.minTs, pkt.timestampNs);
+    bundle.maxTs = std::max(bundle.maxTs, pkt.timestampNs);
+    bundle.bytes += pkt.length;
+  }
+
+  NetworkTraceState* incr_state = ctx.GetIncrementalState();
+  for (const auto& kv : bundles) {
+    const BundleKey& key = kv.first;
+    const BundleDetails& details = kv.second;
+
+    auto dst = ctx.NewTracePacket();
+    dst->set_timestamp(details.minTs);
+
+    // Incremental state is only used when interning. Set the flag based on
+    // whether state was cleared. Leave the flag empty in non-intern configs.
+    if (mInternLimit > 0) {
+      if (incr_state->cleared) {
+        dst->set_sequence_flags(TracePacket::SEQ_INCREMENTAL_STATE_CLEARED);
+        incr_state->cleared = false;
+      } else {
+        dst->set_sequence_flags(TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
+      }
+    }
+
+    auto* event = FillWithInterning(incr_state, key, dst.get());
+
+    int count = details.time_and_len.size();
+    if (!mAggregationThreshold || count < mAggregationThreshold) {
+      protozero::PackedVarInt offsets;
+      protozero::PackedVarInt lengths;
+      for (const auto& kv : details.time_and_len) {
+        offsets.Append(kv.first - details.minTs);
+        lengths.Append(kv.second);
+      }
+
+      event->set_packet_timestamps(offsets);
+      event->set_packet_lengths(lengths);
+    } else {
+      event->set_total_duration(details.maxTs - details.minTs);
+      event->set_total_length(details.bytes);
+      event->set_total_packets(count);
+    }
+  }
+}
+
+void NetworkTraceHandler::Fill(const PacketTrace& src,
+                               NetworkPacketEvent* event) {
+  event->set_direction(src.egress ? TrafficDirection::DIR_EGRESS
+                                  : TrafficDirection::DIR_INGRESS);
+  event->set_uid(src.uid);
+  event->set_tag(src.tag);
+
+  if (!mDropLocalPort) {
+    event->set_local_port(ntohs(src.egress ? src.sport : src.dport));
+  }
+  if (!mDropRemotePort) {
+    event->set_remote_port(ntohs(src.egress ? src.dport : src.sport));
+  }
+  if (!mDropTcpFlags) {
+    event->set_tcp_flags(src.tcpFlags);
+  }
+
+  event->set_ip_proto(src.ipProto);
+
+  char ifname[IF_NAMESIZE] = {};
+  if (if_indextoname(src.ifindex, ifname) == ifname) {
+    event->set_interface(std::string(ifname));
+  } else {
+    event->set_interface("error");
+  }
+}
+
+NetworkPacketBundle* NetworkTraceHandler::FillWithInterning(
+    NetworkTraceState* state, const BundleKey& key, TracePacket* dst) {
+  uint64_t iid = 0;
+  bool found = false;
+
+  if (state->iids.size() < mInternLimit) {
+    auto [iter, success] = state->iids.try_emplace(key, state->iids.size() + 1);
+    iid = iter->second;
+    found = true;
+
+    if (success) {
+      // If we successfully empaced, record the newly interned data.
+      auto* packet_context = dst->set_interned_data()->add_packet_context();
+      Fill(key, packet_context->set_ctx());
+      packet_context->set_iid(iid);
+    }
+  } else {
+    auto iter = state->iids.find(key);
+    if (iter != state->iids.end()) {
+      iid = iter->second;
+      found = true;
+    }
+  }
+
+  auto* event = dst->set_network_packet_bundle();
+  if (found) {
+    event->set_iid(iid);
+  } else {
+    Fill(key, event->set_ctx());
+  }
+
+  return event;
+}
+
+}  // namespace bpf
+}  // namespace android
diff --git a/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp b/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp
new file mode 100644
index 0000000..f2c1a86
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <vector>
+
+#include "netdbpf/NetworkTraceHandler.h"
+#include "protos/perfetto/config/android/network_trace_config.gen.h"
+#include "protos/perfetto/trace/android/network_trace.pb.h"
+#include "protos/perfetto/trace/trace.pb.h"
+#include "protos/perfetto/trace/trace_packet.pb.h"
+
+namespace android {
+namespace bpf {
+using ::perfetto::protos::NetworkPacketEvent;
+using ::perfetto::protos::NetworkPacketTraceConfig;
+using ::perfetto::protos::Trace;
+using ::perfetto::protos::TracePacket;
+using ::perfetto::protos::TrafficDirection;
+
+class NetworkTraceHandlerTest : public testing::Test {
+ protected:
+  // Starts a tracing session with the handler under test.
+  std::unique_ptr<perfetto::TracingSession> StartTracing(
+      NetworkPacketTraceConfig settings) {
+    perfetto::TracingInitArgs args;
+    args.backends = perfetto::kInProcessBackend;
+    perfetto::Tracing::Initialize(args);
+
+    perfetto::DataSourceDescriptor dsd;
+    dsd.set_name("test.network_packets");
+    NetworkTraceHandler::Register(dsd, /*isTest=*/true);
+
+    perfetto::TraceConfig cfg;
+    cfg.add_buffers()->set_size_kb(1024);
+    auto* config = cfg.add_data_sources()->mutable_config();
+    config->set_name("test.network_packets");
+    config->set_network_packet_trace_config_raw(settings.SerializeAsString());
+
+    auto session = perfetto::Tracing::NewTrace(perfetto::kInProcessBackend);
+    session->Setup(cfg);
+    session->StartBlocking();
+    return session;
+  }
+
+  // Stops the trace session and reports all relevant trace packets.
+  bool StopTracing(perfetto::TracingSession* session,
+                   std::vector<TracePacket>* output) {
+    session->StopBlocking();
+
+    Trace trace;
+    std::vector<char> raw_trace = session->ReadTraceBlocking();
+    if (!trace.ParseFromArray(raw_trace.data(), raw_trace.size())) {
+      ADD_FAILURE() << "trace.ParseFromArray failed";
+      return false;
+    }
+
+    // This is a real trace and includes irrelevant trace packets such as trace
+    // metadata. The following strips the results to just the packets we want.
+    for (const auto& pkt : trace.packet()) {
+      if (pkt.has_network_packet() || pkt.has_network_packet_bundle()) {
+        output->emplace_back(pkt);
+      }
+    }
+
+    return true;
+  }
+
+  // This runs a trace with a single call to Write.
+  bool TraceAndSortPackets(const std::vector<PacketTrace>& input,
+                           std::vector<TracePacket>* output,
+                           NetworkPacketTraceConfig config = {}) {
+    auto session = StartTracing(config);
+    NetworkTraceHandler::Trace([&](NetworkTraceHandler::TraceContext ctx) {
+      ctx.GetDataSourceLocked()->Write(input, ctx);
+      ctx.Flush();
+    });
+
+    if (!StopTracing(session.get(), output)) {
+      return false;
+    }
+
+    // Sort to provide deterministic ordering regardless of Perfetto internals
+    // or implementation-defined (e.g. hash map) reshuffling.
+    std::sort(output->begin(), output->end(),
+              [](const TracePacket& a, const TracePacket& b) {
+                return a.timestamp() < b.timestamp();
+              });
+
+    return true;
+  }
+};
+
+TEST_F(NetworkTraceHandlerTest, WriteBasicFields) {
+  std::vector<PacketTrace> input = {
+      PacketTrace{
+          .timestampNs = 1000,
+          .length = 100,
+          .uid = 10,
+          .tag = 123,
+          .ipProto = 6,
+          .tcpFlags = 1,
+      },
+  };
+
+  std::vector<TracePacket> events;
+  ASSERT_TRUE(TraceAndSortPackets(input, &events));
+
+  ASSERT_EQ(events.size(), 1);
+  EXPECT_THAT(events[0].timestamp(), 1000);
+  EXPECT_THAT(events[0].network_packet().uid(), 10);
+  EXPECT_THAT(events[0].network_packet().tag(), 123);
+  EXPECT_THAT(events[0].network_packet().ip_proto(), 6);
+  EXPECT_THAT(events[0].network_packet().tcp_flags(), 1);
+  EXPECT_THAT(events[0].network_packet().length(), 100);
+  EXPECT_THAT(events[0].has_sequence_flags(), false);
+}
+
+TEST_F(NetworkTraceHandlerTest, WriteDirectionAndPorts) {
+  std::vector<PacketTrace> input = {
+      PacketTrace{
+          .timestampNs = 1,
+          .sport = htons(8080),
+          .dport = htons(443),
+          .egress = true,
+      },
+      PacketTrace{
+          .timestampNs = 2,
+          .sport = htons(443),
+          .dport = htons(8080),
+          .egress = false,
+      },
+  };
+
+  std::vector<TracePacket> events;
+  ASSERT_TRUE(TraceAndSortPackets(input, &events));
+
+  ASSERT_EQ(events.size(), 2);
+  EXPECT_THAT(events[0].network_packet().local_port(), 8080);
+  EXPECT_THAT(events[0].network_packet().remote_port(), 443);
+  EXPECT_THAT(events[0].network_packet().direction(),
+              TrafficDirection::DIR_EGRESS);
+  EXPECT_THAT(events[1].network_packet().local_port(), 8080);
+  EXPECT_THAT(events[1].network_packet().remote_port(), 443);
+  EXPECT_THAT(events[1].network_packet().direction(),
+              TrafficDirection::DIR_INGRESS);
+}
+
+TEST_F(NetworkTraceHandlerTest, BasicBundling) {
+  // TODO: remove this once bundling becomes default. Until then, set arbitrary
+  // aggregation threshold to enable bundling.
+  NetworkPacketTraceConfig config;
+  config.set_aggregation_threshold(10);
+
+  std::vector<PacketTrace> input = {
+      PacketTrace{.uid = 123, .timestampNs = 2, .length = 200},
+      PacketTrace{.uid = 123, .timestampNs = 1, .length = 100},
+      PacketTrace{.uid = 123, .timestampNs = 4, .length = 300},
+
+      PacketTrace{.uid = 456, .timestampNs = 2, .length = 400},
+      PacketTrace{.uid = 456, .timestampNs = 4, .length = 100},
+  };
+
+  std::vector<TracePacket> events;
+  ASSERT_TRUE(TraceAndSortPackets(input, &events, config));
+
+  ASSERT_EQ(events.size(), 2);
+
+  EXPECT_THAT(events[0].timestamp(), 1);
+  EXPECT_THAT(events[0].network_packet_bundle().ctx().uid(), 123);
+  EXPECT_THAT(events[0].network_packet_bundle().packet_lengths(),
+              testing::ElementsAre(200, 100, 300));
+  EXPECT_THAT(events[0].network_packet_bundle().packet_timestamps(),
+              testing::ElementsAre(1, 0, 3));
+
+  EXPECT_THAT(events[1].timestamp(), 2);
+  EXPECT_THAT(events[1].network_packet_bundle().ctx().uid(), 456);
+  EXPECT_THAT(events[1].network_packet_bundle().packet_lengths(),
+              testing::ElementsAre(400, 100));
+  EXPECT_THAT(events[1].network_packet_bundle().packet_timestamps(),
+              testing::ElementsAre(0, 2));
+}
+
+TEST_F(NetworkTraceHandlerTest, AggregationThreshold) {
+  // With an aggregation threshold of 3, the set of packets with uid=123 will
+  // be aggregated (3>=3) whereas packets with uid=456 get per-packet info.
+  NetworkPacketTraceConfig config;
+  config.set_aggregation_threshold(3);
+
+  std::vector<PacketTrace> input = {
+      PacketTrace{.uid = 123, .timestampNs = 2, .length = 200},
+      PacketTrace{.uid = 123, .timestampNs = 1, .length = 100},
+      PacketTrace{.uid = 123, .timestampNs = 4, .length = 300},
+
+      PacketTrace{.uid = 456, .timestampNs = 2, .length = 400},
+      PacketTrace{.uid = 456, .timestampNs = 4, .length = 100},
+  };
+
+  std::vector<TracePacket> events;
+  ASSERT_TRUE(TraceAndSortPackets(input, &events, config));
+
+  ASSERT_EQ(events.size(), 2);
+
+  EXPECT_EQ(events[0].timestamp(), 1);
+  EXPECT_EQ(events[0].network_packet_bundle().ctx().uid(), 123);
+  EXPECT_EQ(events[0].network_packet_bundle().total_duration(), 3);
+  EXPECT_EQ(events[0].network_packet_bundle().total_packets(), 3);
+  EXPECT_EQ(events[0].network_packet_bundle().total_length(), 600);
+
+  EXPECT_EQ(events[1].timestamp(), 2);
+  EXPECT_EQ(events[1].network_packet_bundle().ctx().uid(), 456);
+  EXPECT_THAT(events[1].network_packet_bundle().packet_lengths(),
+              testing::ElementsAre(400, 100));
+  EXPECT_THAT(events[1].network_packet_bundle().packet_timestamps(),
+              testing::ElementsAre(0, 2));
+}
+
+TEST_F(NetworkTraceHandlerTest, DropLocalPort) {
+  NetworkPacketTraceConfig config;
+  config.set_drop_local_port(true);
+  config.set_aggregation_threshold(10);
+
+  __be16 a = htons(10000);
+  __be16 b = htons(10001);
+  std::vector<PacketTrace> input = {
+      // Recall that local is `src` for egress and `dst` for ingress.
+      PacketTrace{.timestampNs = 1, .length = 2, .egress = true, .sport = a},
+      PacketTrace{.timestampNs = 2, .length = 4, .egress = false, .dport = a},
+      PacketTrace{.timestampNs = 3, .length = 6, .egress = true, .sport = b},
+      PacketTrace{.timestampNs = 4, .length = 8, .egress = false, .dport = b},
+  };
+
+  std::vector<TracePacket> events;
+  ASSERT_TRUE(TraceAndSortPackets(input, &events, config));
+  ASSERT_EQ(events.size(), 2);
+
+  // Despite having different local ports, drop and bundle by remaining fields.
+  EXPECT_EQ(events[0].network_packet_bundle().ctx().direction(),
+            TrafficDirection::DIR_EGRESS);
+  EXPECT_THAT(events[0].network_packet_bundle().packet_lengths(),
+              testing::ElementsAre(2, 6));
+
+  EXPECT_EQ(events[1].network_packet_bundle().ctx().direction(),
+            TrafficDirection::DIR_INGRESS);
+  EXPECT_THAT(events[1].network_packet_bundle().packet_lengths(),
+              testing::ElementsAre(4, 8));
+
+  // Local port shouldn't be in output.
+  EXPECT_FALSE(events[0].network_packet_bundle().ctx().has_local_port());
+  EXPECT_FALSE(events[1].network_packet_bundle().ctx().has_local_port());
+}
+
+TEST_F(NetworkTraceHandlerTest, DropRemotePort) {
+  NetworkPacketTraceConfig config;
+  config.set_drop_remote_port(true);
+  config.set_aggregation_threshold(10);
+
+  __be16 a = htons(443);
+  __be16 b = htons(80);
+  std::vector<PacketTrace> input = {
+      // Recall that remote is `dst` for egress and `src` for ingress.
+      PacketTrace{.timestampNs = 1, .length = 2, .egress = true, .dport = a},
+      PacketTrace{.timestampNs = 2, .length = 4, .egress = false, .sport = a},
+      PacketTrace{.timestampNs = 3, .length = 6, .egress = true, .dport = b},
+      PacketTrace{.timestampNs = 4, .length = 8, .egress = false, .sport = b},
+  };
+
+  std::vector<TracePacket> events;
+  ASSERT_TRUE(TraceAndSortPackets(input, &events, config));
+  ASSERT_EQ(events.size(), 2);
+
+  // Despite having different remote ports, drop and bundle by remaining fields.
+  EXPECT_EQ(events[0].network_packet_bundle().ctx().direction(),
+            TrafficDirection::DIR_EGRESS);
+  EXPECT_THAT(events[0].network_packet_bundle().packet_lengths(),
+              testing::ElementsAre(2, 6));
+
+  EXPECT_EQ(events[1].network_packet_bundle().ctx().direction(),
+            TrafficDirection::DIR_INGRESS);
+  EXPECT_THAT(events[1].network_packet_bundle().packet_lengths(),
+              testing::ElementsAre(4, 8));
+
+  // Remote port shouldn't be in output.
+  EXPECT_FALSE(events[0].network_packet_bundle().ctx().has_remote_port());
+  EXPECT_FALSE(events[1].network_packet_bundle().ctx().has_remote_port());
+}
+
+TEST_F(NetworkTraceHandlerTest, DropTcpFlags) {
+  NetworkPacketTraceConfig config;
+  config.set_drop_tcp_flags(true);
+  config.set_aggregation_threshold(10);
+
+  std::vector<PacketTrace> input = {
+      PacketTrace{.timestampNs = 1, .uid = 123, .length = 1, .tcpFlags = 1},
+      PacketTrace{.timestampNs = 2, .uid = 123, .length = 2, .tcpFlags = 2},
+      PacketTrace{.timestampNs = 3, .uid = 456, .length = 3, .tcpFlags = 1},
+      PacketTrace{.timestampNs = 4, .uid = 456, .length = 4, .tcpFlags = 2},
+  };
+
+  std::vector<TracePacket> events;
+  ASSERT_TRUE(TraceAndSortPackets(input, &events, config));
+
+  ASSERT_EQ(events.size(), 2);
+
+  // Despite having different tcp flags, drop and bundle by remaining fields.
+  EXPECT_EQ(events[0].network_packet_bundle().ctx().uid(), 123);
+  EXPECT_THAT(events[0].network_packet_bundle().packet_lengths(),
+              testing::ElementsAre(1, 2));
+
+  EXPECT_EQ(events[1].network_packet_bundle().ctx().uid(), 456);
+  EXPECT_THAT(events[1].network_packet_bundle().packet_lengths(),
+              testing::ElementsAre(3, 4));
+
+  // Tcp flags shouldn't be in output.
+  EXPECT_FALSE(events[0].network_packet_bundle().ctx().has_tcp_flags());
+  EXPECT_FALSE(events[1].network_packet_bundle().ctx().has_tcp_flags());
+}
+
+TEST_F(NetworkTraceHandlerTest, Interning) {
+  NetworkPacketTraceConfig config;
+  config.set_intern_limit(2);
+
+  // The test writes 4 packets coming from three sources (uids). With an intern
+  // limit of 2, the first two sources should be interned. This test splits this
+  // into individual writes since internally an unordered map is used and would
+  // otherwise non-deterministically choose what to intern (this is fine for
+  // real use, but not good for test assertions).
+  std::vector<std::vector<PacketTrace>> inputs = {
+      {PacketTrace{.timestampNs = 1, .uid = 123}},
+      {PacketTrace{.timestampNs = 2, .uid = 456}},
+      {PacketTrace{.timestampNs = 3, .uid = 789}},
+      {PacketTrace{.timestampNs = 4, .uid = 123}},
+  };
+
+  auto session = StartTracing(config);
+
+  NetworkTraceHandler::Trace([&](NetworkTraceHandler::TraceContext ctx) {
+    ctx.GetDataSourceLocked()->Write(inputs[0], ctx);
+    ctx.GetDataSourceLocked()->Write(inputs[1], ctx);
+    ctx.GetDataSourceLocked()->Write(inputs[2], ctx);
+    ctx.GetDataSourceLocked()->Write(inputs[3], ctx);
+    ctx.Flush();
+  });
+
+  std::vector<TracePacket> events;
+  ASSERT_TRUE(StopTracing(session.get(), &events));
+
+  ASSERT_EQ(events.size(), 4);
+
+  // First time seen, emit new interned data, bundle uses iid instead of ctx.
+  EXPECT_EQ(events[0].network_packet_bundle().iid(), 1);
+  ASSERT_EQ(events[0].interned_data().packet_context().size(), 1);
+  EXPECT_EQ(events[0].interned_data().packet_context(0).iid(), 1);
+  EXPECT_EQ(events[0].interned_data().packet_context(0).ctx().uid(), 123);
+  EXPECT_EQ(events[0].sequence_flags(),
+            TracePacket::SEQ_INCREMENTAL_STATE_CLEARED);
+
+  // First time seen, emit new interned data, bundle uses iid instead of ctx.
+  EXPECT_EQ(events[1].network_packet_bundle().iid(), 2);
+  ASSERT_EQ(events[1].interned_data().packet_context().size(), 1);
+  EXPECT_EQ(events[1].interned_data().packet_context(0).iid(), 2);
+  EXPECT_EQ(events[1].interned_data().packet_context(0).ctx().uid(), 456);
+  EXPECT_EQ(events[1].sequence_flags(),
+            TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
+
+  // Not enough room in intern table (limit 2), inline the context.
+  EXPECT_EQ(events[2].network_packet_bundle().ctx().uid(), 789);
+  EXPECT_EQ(events[2].interned_data().packet_context().size(), 0);
+  EXPECT_EQ(events[2].sequence_flags(),
+            TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
+
+  // Second time seen, no need to re-emit interned data, only record iid.
+  EXPECT_EQ(events[3].network_packet_bundle().iid(), 1);
+  EXPECT_EQ(events[3].interned_data().packet_context().size(), 0);
+  EXPECT_EQ(events[3].sequence_flags(),
+            TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
+}
+
+}  // namespace bpf
+}  // namespace android
diff --git a/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp b/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp
new file mode 100644
index 0000000..d538368
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/NetworkTracePoller.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "NetworkTrace"
+#define ATRACE_TAG ATRACE_TAG_NETWORK
+
+#include "netdbpf/NetworkTracePoller.h"
+
+#include <bpf/BpfUtils.h>
+#include <cutils/trace.h>
+#include <log/log.h>
+#include <perfetto/tracing/platform.h>
+#include <perfetto/tracing/tracing.h>
+
+namespace android {
+namespace bpf {
+namespace internal {
+
+void NetworkTracePoller::SchedulePolling() {
+  // Schedules another run of ourselves to recursively poll periodically.
+  mTaskRunner->PostDelayedTask(
+      [this]() {
+        mMutex.lock();
+        SchedulePolling();
+        ConsumeAllLocked();
+        mMutex.unlock();
+      },
+      mPollMs);
+}
+
+bool NetworkTracePoller::Start(uint32_t pollMs) {
+  ALOGD("Starting datasource");
+
+  std::scoped_lock<std::mutex> lock(mMutex);
+  if (mSessionCount > 0) {
+    if (mPollMs != pollMs) {
+      // Nothing technical prevents mPollMs from changing, it's just unclear
+      // what the right behavior is. Taking the min of active values could poll
+      // too frequently giving some sessions too much data. Taking the max could
+      // be too infrequent. For now, do nothing.
+      ALOGI("poll_ms can't be changed while running, ignoring poll_ms=%d",
+            pollMs);
+    }
+    mSessionCount++;
+    return true;
+  }
+
+  auto status = mConfigurationMap.init(PACKET_TRACE_ENABLED_MAP_PATH);
+  if (!status.ok()) {
+    ALOGW("Failed to bind config map: %s", status.error().message().c_str());
+    return false;
+  }
+
+  auto rb = BpfRingbuf<PacketTrace>::Create(PACKET_TRACE_RINGBUF_PATH);
+  if (!rb.ok()) {
+    ALOGW("Failed to create ringbuf: %s", rb.error().message().c_str());
+    return false;
+  }
+
+  mRingBuffer = std::move(*rb);
+
+  auto res = mConfigurationMap.writeValue(0, true, BPF_ANY);
+  if (!res.ok()) {
+    ALOGW("Failed to enable tracing: %s", res.error().message().c_str());
+    return false;
+  }
+
+  // Start a task runner to run ConsumeAll every mPollMs milliseconds.
+  mTaskRunner = perfetto::Platform::GetDefaultPlatform()->CreateTaskRunner({});
+  mPollMs = pollMs;
+  SchedulePolling();
+
+  mSessionCount++;
+  return true;
+}
+
+bool NetworkTracePoller::Stop() {
+  ALOGD("Stopping datasource");
+
+  std::scoped_lock<std::mutex> lock(mMutex);
+  if (mSessionCount == 0) return false;  // This should never happen
+
+  // If this isn't the last session, don't clean up yet.
+  if (--mSessionCount > 0) return true;
+
+  auto res = mConfigurationMap.writeValue(0, false, BPF_ANY);
+  if (!res.ok()) {
+    ALOGW("Failed to disable tracing: %s", res.error().message().c_str());
+  }
+
+  // Make sure everything in the system has actually seen the 'false' we just
+  // wrote, things should now be well and truly disabled.
+  synchronizeKernelRCU();
+
+  // Drain remaining events from the ring buffer now that tracing is disabled.
+  // This prevents the next trace from seeing stale events and allows writing
+  // the last batch of events to Perfetto.
+  ConsumeAllLocked();
+
+  mTaskRunner.reset();
+  mRingBuffer.reset();
+
+  return res.ok();
+}
+
+bool NetworkTracePoller::ConsumeAll() {
+  std::scoped_lock<std::mutex> lock(mMutex);
+  return ConsumeAllLocked();
+}
+
+bool NetworkTracePoller::ConsumeAllLocked() {
+  if (mRingBuffer == nullptr) {
+    ALOGW("Tracing is not active");
+    return false;
+  }
+
+  std::vector<PacketTrace> packets;
+  base::Result<int> ret = mRingBuffer->ConsumeAll(
+      [&](const PacketTrace& pkt) { packets.push_back(pkt); });
+  if (!ret.ok()) {
+    ALOGW("Failed to poll ringbuf: %s", ret.error().message().c_str());
+    return false;
+  }
+
+  ATRACE_INT("NetworkTracePackets", packets.size());
+
+  mCallback(packets);
+
+  return true;
+}
+
+}  // namespace internal
+}  // namespace bpf
+}  // namespace android
diff --git a/service-t/native/libs/libnetworkstats/NetworkTracePollerTest.cpp b/service-t/native/libs/libnetworkstats/NetworkTracePollerTest.cpp
new file mode 100644
index 0000000..df07bbe
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/NetworkTracePollerTest.cpp
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/unique_fd.h>
+#include <android/multinetwork.h>
+#include <arpa/inet.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <inttypes.h>
+#include <net/if.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <thread>
+#include <vector>
+
+#include "netdbpf/NetworkTracePoller.h"
+
+using ::testing::AllOf;
+using ::testing::AnyOf;
+using ::testing::Each;
+using ::testing::Eq;
+using ::testing::Field;
+using ::testing::Test;
+
+namespace android {
+namespace bpf {
+namespace internal {
+// Use uint32 max to cause the handler to never Loop. Instead, the tests will
+// manually drive things by calling ConsumeAll explicitly.
+constexpr uint32_t kNeverPoll = std::numeric_limits<uint32_t>::max();
+
+__be16 bindAndListen(int s) {
+  sockaddr_in sin = {.sin_family = AF_INET};
+  socklen_t len = sizeof(sin);
+  if (bind(s, (sockaddr*)&sin, sizeof(sin))) return 0;
+  if (listen(s, 1)) return 0;
+  if (getsockname(s, (sockaddr*)&sin, &len)) return 0;
+  return sin.sin_port;
+}
+
+// This takes tcp flag constants from the standard library and makes them usable
+// with the flags we get from BPF. The standard library flags are big endian
+// whereas the BPF flags are reported in host byte order. BPF also trims the
+// flags down to the 8 single-bit flag bits (fin, syn, rst, etc).
+constexpr inline uint8_t FlagToHost(__be32 be_unix_flags) {
+  return ntohl(be_unix_flags) >> 16;
+}
+
+// Pretty prints all fields for a list of packets (useful for debugging).
+struct PacketPrinter {
+  const std::vector<PacketTrace>& data;
+  static constexpr char kTcpFlagNames[] = "FSRPAUEC";
+
+  friend std::ostream& operator<<(std::ostream& os, const PacketPrinter& d) {
+    os << "Packet count: " << d.data.size();
+    for (const PacketTrace& info : d.data) {
+      os << "\nifidx=" << info.ifindex;
+      os << ", len=" << info.length;
+      os << ", uid=" << info.uid;
+      os << ", tag=" << info.tag;
+      os << ", sport=" << info.sport;
+      os << ", dport=" << info.dport;
+      os << ", direction=" << (info.egress ? "egress" : "ingress");
+      os << ", proto=" << static_cast<int>(info.ipProto);
+      os << ", ip=" << static_cast<int>(info.ipVersion);
+      os << ", flags=";
+      for (int i = 0; i < 8; i++) {
+        os << ((info.tcpFlags & (1 << i)) ? kTcpFlagNames[i] : '.');
+      }
+    }
+    return os;
+  }
+};
+
+class NetworkTracePollerTest : public testing::Test {
+ protected:
+  void SetUp() {
+    if (access(PACKET_TRACE_RINGBUF_PATH, R_OK)) {
+      GTEST_SKIP() << "Network tracing is not enabled/loaded on this build.";
+    }
+    if (sizeof(void*) != 8) {
+      GTEST_SKIP() << "Network tracing requires 64-bit build.";
+    }
+  }
+};
+
+TEST_F(NetworkTracePollerTest, PollWhileInactive) {
+  NetworkTracePoller handler([&](const std::vector<PacketTrace>& pkt) {});
+
+  // One succeed after start and before stop.
+  EXPECT_FALSE(handler.ConsumeAll());
+  ASSERT_TRUE(handler.Start(kNeverPoll));
+  EXPECT_TRUE(handler.ConsumeAll());
+  ASSERT_TRUE(handler.Stop());
+  EXPECT_FALSE(handler.ConsumeAll());
+}
+
+TEST_F(NetworkTracePollerTest, ConcurrentSessions) {
+  // Simulate two concurrent sessions (two starts followed by two stops). Check
+  // that tracing is stopped only after both sessions finish.
+  NetworkTracePoller handler([&](const std::vector<PacketTrace>& pkt) {});
+
+  ASSERT_TRUE(handler.Start(kNeverPoll));
+  EXPECT_TRUE(handler.ConsumeAll());
+
+  ASSERT_TRUE(handler.Start(kNeverPoll));
+  EXPECT_TRUE(handler.ConsumeAll());
+
+  ASSERT_TRUE(handler.Stop());
+  EXPECT_TRUE(handler.ConsumeAll());
+
+  ASSERT_TRUE(handler.Stop());
+  EXPECT_FALSE(handler.ConsumeAll());
+}
+
+TEST_F(NetworkTracePollerTest, TraceTcpSession) {
+  __be16 server_port = 0;
+  std::vector<PacketTrace> packets, unmatched;
+
+  // Record all packets with the bound address and current uid. This callback is
+  // involked only within ConsumeAll, at which point the port should have
+  // already been filled in and all packets have been processed.
+  NetworkTracePoller handler([&](const std::vector<PacketTrace>& pkts) {
+    for (const PacketTrace& pkt : pkts) {
+      if ((pkt.sport == server_port || pkt.dport == server_port) &&
+          pkt.uid == getuid()) {
+        packets.push_back(pkt);
+      } else {
+        // There may be spurious packets not caused by the test. These are only
+        // captured so that we can report them to help debug certain errors.
+        unmatched.push_back(pkt);
+      }
+    }
+  });
+
+  ASSERT_TRUE(handler.Start(kNeverPoll));
+  const uint32_t kClientTag = 2468;
+  const uint32_t kServerTag = 1357;
+
+  // Go through a typical connection sequence between two v4 sockets using tcp.
+  // This covers connection handshake, shutdown, and one data packet.
+  {
+    android::base::unique_fd clientsocket(socket(AF_INET, SOCK_STREAM, 0));
+    ASSERT_NE(-1, clientsocket) << "Failed to open client socket";
+    ASSERT_EQ(android_tag_socket(clientsocket, kClientTag), 0);
+
+    android::base::unique_fd serversocket(socket(AF_INET, SOCK_STREAM, 0));
+    ASSERT_NE(-1, serversocket) << "Failed to open server socket";
+    ASSERT_EQ(android_tag_socket(serversocket, kServerTag), 0);
+
+    server_port = bindAndListen(serversocket);
+    ASSERT_NE(0, server_port) << "Can't bind to server port";
+
+    sockaddr_in addr = {.sin_family = AF_INET, .sin_port = server_port};
+    ASSERT_EQ(0, connect(clientsocket, (sockaddr*)&addr, sizeof(addr)))
+        << "connect to loopback failed: " << strerror(errno);
+
+    int accepted = accept(serversocket, nullptr, nullptr);
+    ASSERT_NE(-1, accepted) << "accept connection failed: " << strerror(errno);
+
+    const char data[] = "abcdefghijklmnopqrstuvwxyz";
+    EXPECT_EQ(send(clientsocket, data, sizeof(data), 0), sizeof(data))
+        << "failed to send message: " << strerror(errno);
+
+    char buff[100] = {};
+    EXPECT_EQ(recv(accepted, buff, sizeof(buff), 0), sizeof(data))
+        << "failed to receive message: " << strerror(errno);
+
+    EXPECT_EQ(std::string(data), std::string(buff));
+  }
+
+  // Poll until we get all the packets (typically we get it first try).
+  for (int attempt = 0; attempt < 10; attempt++) {
+    ASSERT_TRUE(handler.ConsumeAll());
+    if (packets.size() >= 12) break;
+    std::this_thread::sleep_for(std::chrono::milliseconds(5));
+  }
+
+  ASSERT_TRUE(handler.Stop());
+
+  // There are 12 packets in total (6 messages: each seen by client & server):
+  // 1. Client connects to server with syn
+  // 2. Server responds with syn ack
+  // 3. Client responds with ack
+  // 4. Client sends data with psh ack
+  // 5. Server acks the data packet
+  // 6. Client closes connection with fin ack
+  ASSERT_EQ(packets.size(), 12)
+      << PacketPrinter{packets}
+      << "\nUnmatched packets: " << PacketPrinter{unmatched};
+
+  // All packets should be TCP packets.
+  EXPECT_THAT(packets, Each(Field(&PacketTrace::ipProto, Eq(IPPROTO_TCP))));
+
+  // Packet 1: client requests connection with server.
+  EXPECT_EQ(packets[0].egress, 1) << PacketPrinter{packets};
+  EXPECT_EQ(packets[0].dport, server_port) << PacketPrinter{packets};
+  EXPECT_EQ(packets[0].tag, kClientTag) << PacketPrinter{packets};
+  EXPECT_EQ(packets[0].tcpFlags, FlagToHost(TCP_FLAG_SYN))
+      << PacketPrinter{packets};
+
+  // Packet 2: server receives request from client.
+  EXPECT_EQ(packets[1].egress, 0) << PacketPrinter{packets};
+  EXPECT_EQ(packets[1].dport, server_port) << PacketPrinter{packets};
+  EXPECT_EQ(packets[1].tag, kServerTag) << PacketPrinter{packets};
+  EXPECT_EQ(packets[1].tcpFlags, FlagToHost(TCP_FLAG_SYN))
+      << PacketPrinter{packets};
+
+  // Packet 3: server replies back with syn ack.
+  EXPECT_EQ(packets[2].egress, 1) << PacketPrinter{packets};
+  EXPECT_EQ(packets[2].sport, server_port) << PacketPrinter{packets};
+  EXPECT_EQ(packets[2].tcpFlags, FlagToHost(TCP_FLAG_SYN | TCP_FLAG_ACK))
+      << PacketPrinter{packets};
+
+  // Packet 4: client receives the server's syn ack.
+  EXPECT_EQ(packets[3].egress, 0) << PacketPrinter{packets};
+  EXPECT_EQ(packets[3].sport, server_port) << PacketPrinter{packets};
+  EXPECT_EQ(packets[3].tcpFlags, FlagToHost(TCP_FLAG_SYN | TCP_FLAG_ACK))
+      << PacketPrinter{packets};
+}
+
+}  // namespace internal
+}  // namespace bpf
+}  // namespace android
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h b/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
index 8ab7e25..133009f 100644
--- a/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/BpfNetworkStats.h
@@ -18,7 +18,7 @@
 #define _BPF_NETWORKSTATS_H
 
 #include <bpf/BpfMap.h>
-#include "bpf_shared.h"
+#include "netd.h"
 
 namespace android {
 namespace bpf {
@@ -26,7 +26,7 @@
 // TODO: set this to a proper value based on the map size;
 constexpr int TAG_STATS_MAP_SOFT_LIMIT = 3;
 constexpr int UID_ALL = -1;
-constexpr int TAG_ALL = -1;
+//constexpr int TAG_ALL = -1;
 constexpr int TAG_NONE = 0;
 constexpr int SET_ALL = -1;
 constexpr int SET_DEFAULT = 0;
@@ -63,9 +63,8 @@
                              const BpfMap<uint32_t, StatsValue>& ifaceStatsMap,
                              const BpfMap<uint32_t, IfaceValue>& ifaceNameMap);
 // For test only
-int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>* lines,
-                                       const std::vector<std::string>& limitIfaces, int limitTag,
-                                       int limitUid, const BpfMap<StatsKey, StatsValue>& statsMap,
+int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>& lines,
+                                       const BpfMap<StatsKey, StatsValue>& statsMap,
                                        const BpfMap<uint32_t, IfaceValue>& ifaceMap);
 // For test only
 int cleanStatsMapInternal(const base::unique_fd& cookieTagMap, const base::unique_fd& tagStatsMap);
@@ -107,18 +106,16 @@
 }
 
 // For test only
-int parseBpfNetworkStatsDevInternal(std::vector<stats_line>* lines,
+int parseBpfNetworkStatsDevInternal(std::vector<stats_line>& lines,
                                     const BpfMap<uint32_t, StatsValue>& statsMap,
                                     const BpfMap<uint32_t, IfaceValue>& ifaceMap);
 
 int bpfGetUidStats(uid_t uid, Stats* stats);
 int bpfGetIfaceStats(const char* iface, Stats* stats);
-int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines,
-                               const std::vector<std::string>& limitIfaces, int limitTag,
-                               int limitUid);
+int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines);
 
 int parseBpfNetworkStatsDev(std::vector<stats_line>* lines);
-void groupNetworkStats(std::vector<stats_line>* lines);
+void groupNetworkStats(std::vector<stats_line>& lines);
 int cleanStatsMap();
 }  // namespace bpf
 }  // namespace android
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h
new file mode 100644
index 0000000..bc10e68
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h
@@ -0,0 +1,110 @@
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <perfetto/base/task_runner.h>
+#include <perfetto/tracing.h>
+
+#include <string>
+#include <unordered_map>
+
+#include "netdbpf/NetworkTracePoller.h"
+
+// For PacketTrace struct definition
+#include "netd.h"
+
+namespace android {
+namespace bpf {
+
+// BundleKeys are PacketTraces where timestamp and length are ignored.
+using BundleKey = PacketTrace;
+
+// BundleKeys are hashed using all fields except timestamp/length.
+struct BundleHash {
+  std::size_t operator()(const BundleKey& a) const;
+};
+
+// BundleKeys are equal if all fields except timestamp/length are equal.
+struct BundleEq {
+  bool operator()(const BundleKey& a, const BundleKey& b) const;
+};
+
+// Track the bundles we've interned and their corresponding intern id (iid). We
+// use IncrementalState (rather than state in the Handler) so that we stay in
+// sync with Perfetto's periodic state clearing (which helps recover from packet
+// loss). When state is cleared, the state object is replaced with a new default
+// constructed instance.
+struct NetworkTraceState {
+  bool cleared = true;
+  std::unordered_map<BundleKey, uint64_t, BundleHash, BundleEq> iids;
+};
+
+// Inject our custom incremental state type using type traits.
+struct NetworkTraceTraits : public perfetto::DefaultDataSourceTraits {
+  using IncrementalStateType = NetworkTraceState;
+};
+
+// NetworkTraceHandler implements the android.network_packets data source. This
+// class is registered with Perfetto and is instantiated when tracing starts and
+// destroyed when tracing ends. There is one instance per trace session.
+class NetworkTraceHandler
+    : public perfetto::DataSource<NetworkTraceHandler, NetworkTraceTraits> {
+ public:
+  // Registers this DataSource.
+  static void RegisterDataSource();
+
+  // Connects to the system Perfetto daemon and registers the trace handler.
+  static void InitPerfettoTracing();
+
+  // When isTest is true, skip non-hermetic code.
+  NetworkTraceHandler(bool isTest = false) : mIsTest(isTest) {}
+
+  // perfetto::DataSource overrides:
+  void OnSetup(const SetupArgs& args) override;
+  void OnStart(const StartArgs&) override;
+  void OnStop(const StopArgs&) override;
+
+  // Writes the packets as Perfetto TracePackets, creating packets as needed
+  // using the provided callback (which allows easy testing).
+  void Write(const std::vector<PacketTrace>& packets,
+             NetworkTraceHandler::TraceContext& ctx);
+
+ private:
+  // Convert a PacketTrace into a Perfetto trace packet.
+  void Fill(const PacketTrace& src,
+            ::perfetto::protos::pbzero::NetworkPacketEvent* event);
+
+  // Fills in contextual information either inline or via interning.
+  ::perfetto::protos::pbzero::NetworkPacketBundle* FillWithInterning(
+      NetworkTraceState* state, const BundleKey& key,
+      ::perfetto::protos::pbzero::TracePacket* dst);
+
+  static internal::NetworkTracePoller sPoller;
+  bool mStarted;
+  bool mIsTest;
+
+  // Values from config, see proto for details.
+  uint32_t mPollMs;
+  uint32_t mInternLimit;
+  uint32_t mAggregationThreshold;
+  bool mDropLocalPort;
+  bool mDropRemotePort;
+  bool mDropTcpFlags;
+};
+
+}  // namespace bpf
+}  // namespace android
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTracePoller.h b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTracePoller.h
new file mode 100644
index 0000000..adde51e
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTracePoller.h
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <perfetto/base/task_runner.h>
+#include <perfetto/tracing.h>
+
+#include <string>
+#include <unordered_map>
+
+#include "android-base/thread_annotations.h"
+#include "bpf/BpfMap.h"
+#include "bpf/BpfRingbuf.h"
+
+// For PacketTrace struct definition
+#include "netd.h"
+
+namespace android {
+namespace bpf {
+namespace internal {
+
+// NetworkTracePoller is responsible for interactions with the BPF ring buffer
+// including polling. This class is an internal helper for NetworkTraceHandler,
+// it is not meant to be used elsewhere.
+class NetworkTracePoller {
+ public:
+  using EventSink = std::function<void(const std::vector<PacketTrace>&)>;
+
+  // Testonly: initialize with a callback capable of intercepting data.
+  NetworkTracePoller(EventSink callback) : mCallback(std::move(callback)) {}
+
+  // Starts tracing with the given poll interval.
+  bool Start(uint32_t pollMs) EXCLUDES(mMutex);
+
+  // Stops tracing and release any held state.
+  bool Stop() EXCLUDES(mMutex);
+
+  // Consumes all available events from the ringbuffer.
+  bool ConsumeAll() EXCLUDES(mMutex);
+
+ private:
+  void SchedulePolling() REQUIRES(mMutex);
+  bool ConsumeAllLocked() REQUIRES(mMutex);
+
+  std::mutex mMutex;
+
+  // Records the number of successfully started active sessions so that only the
+  // first active session attempts setup and only the last cleans up. Note that
+  // the session count will remain zero if Start fails. It is expected that Stop
+  // will not be called for any trace session where Start fails.
+  int mSessionCount GUARDED_BY(mMutex);
+
+  // How often to poll the ring buffer, defined by the trace config.
+  uint32_t mPollMs GUARDED_BY(mMutex);
+
+  // The function to process PacketTrace, typically a Perfetto sink.
+  EventSink mCallback GUARDED_BY(mMutex);
+
+  // The BPF ring buffer handle.
+  std::unique_ptr<BpfRingbuf<PacketTrace>> mRingBuffer GUARDED_BY(mMutex);
+
+  // The packet tracing config map (really a 1-element array).
+  BpfMap<uint32_t, bool> mConfigurationMap GUARDED_BY(mMutex);
+
+  // This must be the last member, causing it to be the first deleted. If it is
+  // not, members required for callbacks can be deleted before it's stopped.
+  std::unique_ptr<perfetto::base::TaskRunner> mTaskRunner GUARDED_BY(mMutex);
+};
+
+}  // namespace internal
+}  // namespace bpf
+}  // namespace android
diff --git a/service-t/src/com/android/server/IpSecService.java b/service-t/src/com/android/server/IpSecService.java
index 6cee08a..a884840 100644
--- a/service-t/src/com/android/server/IpSecService.java
+++ b/service-t/src/com/android/server/IpSecService.java
@@ -17,6 +17,8 @@
 package com.android.server;
 
 import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.net.IpSecManager.FEATURE_IPSEC_TUNNEL_MIGRATION;
 import static android.net.IpSecManager.INVALID_RESOURCE_ID;
 import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.AF_INET6;
@@ -36,6 +38,7 @@
 import android.net.IpSecAlgorithm;
 import android.net.IpSecConfig;
 import android.net.IpSecManager;
+import android.net.IpSecMigrateInfoParcel;
 import android.net.IpSecSpiResponse;
 import android.net.IpSecTransform;
 import android.net.IpSecTransformResponse;
@@ -63,6 +66,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.BinderUtils;
 import com.android.net.module.util.NetdUtils;
 import com.android.net.module.util.PermissionUtils;
@@ -100,6 +104,7 @@
 
     private static final int NETD_FETCH_TIMEOUT_MS = 5000; // ms
     private static final InetAddress INADDR_ANY;
+    private static final InetAddress IN6ADDR_ANY;
 
     @VisibleForTesting static final int MAX_PORT_BIND_ATTEMPTS = 10;
 
@@ -108,6 +113,8 @@
     static {
         try {
             INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
+            IN6ADDR_ANY = InetAddress.getByAddress(
+                    new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
         } catch (UnknownHostException e) {
             throw new RuntimeException(e);
         }
@@ -590,14 +597,19 @@
     }
 
     /**
-     * Tracks an SA in the kernel, and manages cleanup paths. Once a TransformRecord is
-     * created, the SpiRecord that originally tracked the SAs will reliquish the
-     * responsibility of freeing the underlying SA to this class via the mOwnedByTransform flag.
+     * Tracks an SA in the kernel, and manages cleanup paths. Once a TransformRecord is created, the
+     * SpiRecord that originally tracked the SAs will reliquish the responsibility of freeing the
+     * underlying SA to this class via the mOwnedByTransform flag.
+     *
+     * <p>This class is not thread-safe, and expects that that users of this class will ensure
+     * synchronization and thread safety by holding the IpSecService.this instance lock
      */
     private final class TransformRecord extends OwnedResourceRecord {
         private final IpSecConfig mConfig;
         private final SpiRecord mSpi;
         private final EncapSocketRecord mSocket;
+        private String mNewSourceAddress = null;
+        private String mNewDestinationAddress = null;
 
         TransformRecord(
                 int resourceId, IpSecConfig config, SpiRecord spi, EncapSocketRecord socket) {
@@ -621,6 +633,51 @@
             return mSocket;
         }
 
+        @GuardedBy("IpSecService.this")
+        public String getNewSourceAddress() {
+            return mNewSourceAddress;
+        }
+
+        @GuardedBy("IpSecService.this")
+        public String getNewDestinationAddress() {
+            return mNewDestinationAddress;
+        }
+
+        private void verifyTunnelModeOrThrow() {
+            if (mConfig.getMode() != IpSecTransform.MODE_TUNNEL) {
+                throw new UnsupportedOperationException(
+                        "Migration requested/called on non-tunnel-mode transform");
+            }
+        }
+
+        /** Start migrating this transform to new source and destination addresses */
+        @GuardedBy("IpSecService.this")
+        public void startMigration(String newSourceAddress, String newDestinationAddress) {
+            verifyTunnelModeOrThrow();
+            Objects.requireNonNull(newSourceAddress, "newSourceAddress was null");
+            Objects.requireNonNull(newDestinationAddress, "newDestinationAddress was null");
+            mNewSourceAddress = newSourceAddress;
+            mNewDestinationAddress = newDestinationAddress;
+        }
+
+        /** Finish migration and update addresses. */
+        @GuardedBy("IpSecService.this")
+        public void finishMigration() {
+            verifyTunnelModeOrThrow();
+            mConfig.setSourceAddress(mNewSourceAddress);
+            mConfig.setDestinationAddress(mNewDestinationAddress);
+            mNewSourceAddress = null;
+            mNewDestinationAddress = null;
+        }
+
+        /** Return if this transform is going to be migrated. */
+        @GuardedBy("IpSecService.this")
+        public boolean isMigrating() {
+            verifyTunnelModeOrThrow();
+
+            return mNewSourceAddress != null;
+        }
+
         /** always guarded by IpSecService#this */
         @Override
         public void freeUnderlyingResources() {
@@ -961,11 +1018,13 @@
     private final class EncapSocketRecord extends OwnedResourceRecord {
         private FileDescriptor mSocket;
         private final int mPort;
+        private final int mFamily;  // TODO: what about IPV6_ADDRFORM?
 
-        EncapSocketRecord(int resourceId, FileDescriptor socket, int port) {
+        EncapSocketRecord(int resourceId, FileDescriptor socket, int port, int family) {
             super(resourceId);
             mSocket = socket;
             mPort = port;
+            mFamily = family;
         }
 
         /** always guarded by IpSecService#this */
@@ -986,6 +1045,10 @@
             return mSocket;
         }
 
+        public int getFamily() {
+            return mFamily;
+        }
+
         @Override
         protected ResourceTracker getResourceTracker() {
             return getUserRecord().mSocketQuotaTracker;
@@ -1158,15 +1221,16 @@
      * and re-binding, during which the system could *technically* hand that port out to someone
      * else.
      */
-    private int bindToRandomPort(FileDescriptor sockFd) throws IOException {
+    private int bindToRandomPort(FileDescriptor sockFd, int family, InetAddress localAddr)
+            throws IOException {
         for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
             try {
-                FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
-                Os.bind(probeSocket, INADDR_ANY, 0);
+                FileDescriptor probeSocket = Os.socket(family, SOCK_DGRAM, IPPROTO_UDP);
+                Os.bind(probeSocket, localAddr, 0);
                 int port = ((InetSocketAddress) Os.getsockname(probeSocket)).getPort();
                 Os.close(probeSocket);
                 Log.v(TAG, "Binding to port " + port);
-                Os.bind(sockFd, INADDR_ANY, port);
+                Os.bind(sockFd, localAddr, port);
                 return port;
             } catch (ErrnoException e) {
                 // Someone miraculously claimed the port just after we closed probeSocket.
@@ -1208,6 +1272,19 @@
     @Override
     public synchronized IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, IBinder binder)
             throws RemoteException {
+        // Experimental support for IPv6 UDP encap.
+        final int family;
+        final InetAddress localAddr;
+        if (SdkLevel.isAtLeastU() && port >= 65536) {
+            PermissionUtils.enforceNetworkStackPermissionOr(mContext, NETWORK_SETTINGS);
+            port -= 65536;
+            family = AF_INET6;
+            localAddr = IN6ADDR_ANY;
+        } else {
+            family = AF_INET;
+            localAddr = INADDR_ANY;
+        }
+
         if (port != 0 && (port < FREE_PORT_MIN || port > PORT_MAX)) {
             throw new IllegalArgumentException(
                     "Specified port number must be a valid non-reserved UDP port");
@@ -1226,7 +1303,7 @@
 
             FileDescriptor sockFd = null;
             try {
-                sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+                sockFd = Os.socket(family, SOCK_DGRAM, IPPROTO_UDP);
                 pFd = ParcelFileDescriptor.dup(sockFd);
             } finally {
                 IoUtils.closeQuietly(sockFd);
@@ -1243,15 +1320,16 @@
             mNetd.ipSecSetEncapSocketOwner(pFd, callingUid);
             if (port != 0) {
                 Log.v(TAG, "Binding to port " + port);
-                Os.bind(pFd.getFileDescriptor(), INADDR_ANY, port);
+                Os.bind(pFd.getFileDescriptor(), localAddr, port);
             } else {
-                port = bindToRandomPort(pFd.getFileDescriptor());
+                port = bindToRandomPort(pFd.getFileDescriptor(), family, localAddr);
             }
 
             userRecord.mEncapSocketRecords.put(
                     resourceId,
                     new RefcountedResource<EncapSocketRecord>(
-                            new EncapSocketRecord(resourceId, pFd.getFileDescriptor(), port),
+                            new EncapSocketRecord(resourceId, pFd.getFileDescriptor(), port,
+                                    family),
                             binder));
             return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port,
                     pFd.getFileDescriptor());
@@ -1528,6 +1606,7 @@
      */
     private void checkIpSecConfig(IpSecConfig config) {
         UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+        EncapSocketRecord encapSocketRecord = null;
 
         switch (config.getEncapType()) {
             case IpSecTransform.ENCAP_NONE:
@@ -1535,7 +1614,7 @@
             case IpSecTransform.ENCAP_ESPINUDP:
             case IpSecTransform.ENCAP_ESPINUDP_NON_IKE:
                 // Retrieve encap socket record; will throw IllegalArgumentException if not found
-                userRecord.mEncapSocketRecords.getResourceOrThrow(
+                encapSocketRecord = userRecord.mEncapSocketRecords.getResourceOrThrow(
                         config.getEncapSocketResourceId());
 
                 int port = config.getEncapRemotePort();
@@ -1589,10 +1668,9 @@
                             + ") have different address families.");
         }
 
-        // Throw an error if UDP Encapsulation is not used in IPv4.
-        if (config.getEncapType() != IpSecTransform.ENCAP_NONE && sourceFamily != AF_INET) {
+        if (encapSocketRecord != null && encapSocketRecord.getFamily() != destinationFamily) {
             throw new IllegalArgumentException(
-                    "UDP Encapsulation is not supported for this address family");
+                    "UDP encapsulation socket and destination address families must match");
         }
 
         switch (config.getMode()) {
@@ -1630,6 +1708,14 @@
                 android.Manifest.permission.MANAGE_IPSEC_TUNNELS, "IpSecService");
     }
 
+    private void enforceMigrateFeature() {
+        if (!mContext.getPackageManager().hasSystemFeature(FEATURE_IPSEC_TUNNEL_MIGRATION)) {
+            throw new UnsupportedOperationException(
+                    "IPsec Tunnel migration requires"
+                            + " PackageManager.FEATURE_IPSEC_TUNNEL_MIGRATION");
+        }
+    }
+
     private void createOrUpdateTransform(
             IpSecConfig c, int resourceId, SpiRecord spiRecord, EncapSocketRecord socketRecord)
             throws RemoteException {
@@ -1726,6 +1812,45 @@
     }
 
     /**
+     * Migrate an active Tunnel Mode IPsec Transform to new source/destination addresses.
+     *
+     * <p>Begins the process of migrating a transform and cache the new addresses. To complete the
+     * migration once started, callers MUST apply the same transform to the appropriate tunnel using
+     * {@link #applyTunnelModeTransform}. Otherwise, the address update will not be committed and
+     * the transform will still only process traffic between the current source and destination
+     * address. One common use case is that the control plane will start the migration process and
+     * then hand off the transform to the IPsec caller to perform the actual migration when the
+     * tunnel is ready.
+     *
+     * <p>If this method is called multiple times before {@link #applyTunnelModeTransform} is
+     * called, when the transform is applied, it will be migrated to the addresses from the last
+     * call.
+     *
+     * <p>The provided source and destination addresses MUST share the same address family, but they
+     * can have a different family from the current addresses.
+     *
+     * <p>Transform migration is only supported for tunnel mode transforms. Calling this method on
+     * other types of transforms will throw an {@code UnsupportedOperationException}.
+     */
+    @Override
+    public synchronized void migrateTransform(
+            int transformId,
+            String newSourceAddress,
+            String newDestinationAddress,
+            String callingPackage) {
+        Objects.requireNonNull(newSourceAddress, "newSourceAddress was null");
+        Objects.requireNonNull(newDestinationAddress, "newDestinationAddress was null");
+
+        enforceTunnelFeatureAndPermissions(callingPackage);
+        enforceMigrateFeature();
+
+        UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
+        TransformRecord transformInfo =
+                userRecord.mTransformRecords.getResourceOrThrow(transformId);
+        transformInfo.startMigration(newSourceAddress, newDestinationAddress);
+    }
+
+    /**
      * Delete a transport mode transform that was previously allocated by + registered with the
      * system server. If this is called on an inactive (or non-existent) transform, it will not
      * return an error. It's safe to de-allocate transforms that may have already been deleted for
@@ -1784,12 +1909,15 @@
 
     /**
      * Apply an active tunnel mode transform to a TunnelInterface, which will apply the IPsec
-     * security association as a correspondent policy to the provided interface
+     * security association as a correspondent policy to the provided interface.
+     *
+     * <p>If the transform is migrating, migrate the IPsec security association to new
+     * source/destination addresses, and mark the migration as finished.
      */
     @Override
     public synchronized void applyTunnelModeTransform(
-            int tunnelResourceId, int direction,
-            int transformResourceId, String callingPackage) throws RemoteException {
+            int tunnelResourceId, int direction, int transformResourceId, String callingPackage)
+            throws RemoteException {
         enforceTunnelFeatureAndPermissions(callingPackage);
         checkDirection(direction);
 
@@ -1868,6 +1996,32 @@
 
             // Update SA with tunnel mark (ikey or okey based on direction)
             createOrUpdateTransform(c, transformResourceId, spiRecord, socketRecord);
+
+            if (transformInfo.isMigrating()) {
+                if (!mContext.getPackageManager()
+                        .hasSystemFeature(FEATURE_IPSEC_TUNNEL_MIGRATION)) {
+                    Log.wtf(
+                            TAG,
+                            "Attempted to migrate a transform without"
+                                    + " FEATURE_IPSEC_TUNNEL_MIGRATION");
+                }
+
+                for (int selAddrFamily : ADDRESS_FAMILIES) {
+                    final IpSecMigrateInfoParcel migrateInfo =
+                            new IpSecMigrateInfoParcel(
+                                    Binder.getCallingUid(),
+                                    selAddrFamily,
+                                    direction,
+                                    c.getSourceAddress(),
+                                    c.getDestinationAddress(),
+                                    transformInfo.getNewSourceAddress(),
+                                    transformInfo.getNewDestinationAddress(),
+                                    c.getXfrmInterfaceId());
+
+                    mNetd.ipSecMigrate(migrateInfo);
+                }
+                transformInfo.finishMigration();
+            }
         } catch (ServiceSpecificException e) {
             if (e.errorCode == EINVAL) {
                 throw new IllegalArgumentException(e.toString());
diff --git a/service-t/src/com/android/server/NetworkStatsServiceInitializer.java b/service-t/src/com/android/server/NetworkStatsServiceInitializer.java
index 0ea126a..82a4fbd 100644
--- a/service-t/src/com/android/server/NetworkStatsServiceInitializer.java
+++ b/service-t/src/com/android/server/NetworkStatsServiceInitializer.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.net.TrafficStats;
+import android.os.Build;
 import android.util.Log;
 
 import com.android.modules.utils.build.SdkLevel;
@@ -46,6 +47,15 @@
                     /* allowIsolated= */ false);
             TrafficStats.init(getContext());
         }
+
+        // The following code registers the Perfetto Network Trace Handler on non-user builds.
+        // The enhanced tracing is intended to be used for debugging and diagnosing issues. This
+        // is conditional on the build type rather than `isDebuggable` to match the system_server
+        // selinux rules which only allow the Perfetto connection under the same circumstances.
+        if (SdkLevel.isAtLeastU() && !Build.TYPE.equals("user")) {
+            Log.i(TAG, "Initializing network tracing hooks");
+            NetworkStatsService.nativeInitNetworkTracing();
+        }
     }
 
     @Override
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 1226eea..c47c572 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -17,12 +17,21 @@
 package com.android.server;
 
 import static android.net.ConnectivityManager.NETID_UNSET;
+import static android.net.nsd.NsdManager.MDNS_DISCOVERY_MANAGER_EVENT;
 import static android.net.nsd.NsdManager.MDNS_SERVICE_EVENT;
+import static android.net.nsd.NsdManager.RESOLVE_SERVICE_SUCCEEDED;
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
 
+import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
+import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.Intent;
 import android.net.ConnectivityManager;
 import android.net.INetd;
+import android.net.InetAddresses;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.mdns.aidl.DiscoveryInfo;
@@ -36,29 +45,51 @@
 import android.net.nsd.MDnsManager;
 import android.net.nsd.NsdManager;
 import android.net.nsd.NsdServiceInfo;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
-import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.net.module.util.DeviceConfigUtils;
+import com.android.net.module.util.InetAddressUtils;
 import com.android.net.module.util.PermissionUtils;
+import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.mdns.ExecutorProvider;
+import com.android.server.connectivity.mdns.MdnsAdvertiser;
+import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
+import com.android.server.connectivity.mdns.MdnsMultinetworkSocketClient;
+import com.android.server.connectivity.mdns.MdnsSearchOptions;
+import com.android.server.connectivity.mdns.MdnsServiceBrowserListener;
+import com.android.server.connectivity.mdns.MdnsServiceInfo;
+import com.android.server.connectivity.mdns.MdnsSocketProvider;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Network Service Discovery Service handles remote service discovery operation requests by
@@ -69,19 +100,72 @@
 public class NsdService extends INsdManager.Stub {
     private static final String TAG = "NsdService";
     private static final String MDNS_TAG = "mDnsConnector";
+    /**
+     * Enable discovery using the Java DiscoveryManager, instead of the legacy mdnsresponder
+     * implementation.
+     */
+    private static final String MDNS_DISCOVERY_MANAGER_VERSION = "mdns_discovery_manager_version";
+    private static final String LOCAL_DOMAIN_NAME = "local";
 
-    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+    /**
+     * Enable advertising using the Java MdnsAdvertiser, instead of the legacy mdnsresponder
+     * implementation.
+     */
+    private static final String MDNS_ADVERTISER_VERSION = "mdns_advertiser_version";
+
+    /**
+     * Comma-separated list of type:flag mappings indicating the flags to use to allowlist
+     * discovery/advertising using MdnsDiscoveryManager / MdnsAdvertiser for a given type.
+     *
+     * For example _mytype._tcp.local and _othertype._tcp.local would be configured with:
+     * _mytype._tcp:mytype,_othertype._tcp.local:othertype
+     *
+     * In which case the flags:
+     * "mdns_discovery_manager_allowlist_mytype_version",
+     * "mdns_advertiser_allowlist_mytype_version",
+     * "mdns_discovery_manager_allowlist_othertype_version",
+     * "mdns_advertiser_allowlist_othertype_version"
+     * would be used to toggle MdnsDiscoveryManager / MdnsAdvertiser for each type. The flags will
+     * be read with
+     * {@link DeviceConfigUtils#isFeatureEnabled(Context, String, String, String, boolean)}.
+     *
+     * @see #MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX
+     * @see #MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX
+     * @see #MDNS_ALLOWLIST_FLAG_SUFFIX
+     */
+    private static final String MDNS_TYPE_ALLOWLIST_FLAGS = "mdns_type_allowlist_flags";
+
+    private static final String MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX =
+            "mdns_discovery_manager_allowlist_";
+    private static final String MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX =
+            "mdns_advertiser_allowlist_";
+    private static final String MDNS_ALLOWLIST_FLAG_SUFFIX = "_version";
+
+    public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
     private static final long CLEANUP_DELAY_MS = 10000;
     private static final int IFACE_IDX_ANY = 0;
+    private static final SharedLog LOGGER = new SharedLog("serviceDiscovery");
 
     private final Context mContext;
     private final NsdStateMachine mNsdStateMachine;
     private final MDnsManager mMDnsManager;
     private final MDnsEventCallback mMDnsEventCallback;
-    // WARNING : Accessing this value in any thread is not safe, it must only be changed in the
+    @NonNull
+    private final Dependencies mDeps;
+    @NonNull
+    private final MdnsMultinetworkSocketClient mMdnsSocketClient;
+    @NonNull
+    private final MdnsDiscoveryManager mMdnsDiscoveryManager;
+    @NonNull
+    private final MdnsSocketProvider mMdnsSocketProvider;
+    @NonNull
+    private final MdnsAdvertiser mAdvertiser;
+    private final SharedLog mServiceLogs = LOGGER.forSubComponent(TAG);
+    // WARNING : Accessing these values in any thread is not safe, it must only be changed in the
     // state machine thread. If change this outside state machine, it will need to introduce
     // synchronization.
     private boolean mIsDaemonStarted = false;
+    private boolean mIsMonitoringSocketsStarted = false;
 
     /**
      * Clients receiving asynchronous messages
@@ -97,6 +181,137 @@
     private int mUniqueId = 1;
     // The count of the connected legacy clients.
     private int mLegacyClientCount = 0;
+    // The number of client that ever connected.
+    private int mClientNumberId = 1;
+
+    private static class MdnsListener implements MdnsServiceBrowserListener {
+        protected final int mClientId;
+        protected final int mTransactionId;
+        @NonNull
+        protected final NsdServiceInfo mReqServiceInfo;
+        @NonNull
+        protected final String mListenedServiceType;
+
+        MdnsListener(int clientId, int transactionId, @NonNull NsdServiceInfo reqServiceInfo,
+                @NonNull String listenedServiceType) {
+            mClientId = clientId;
+            mTransactionId = transactionId;
+            mReqServiceInfo = reqServiceInfo;
+            mListenedServiceType = listenedServiceType;
+        }
+
+        @NonNull
+        public String getListenedServiceType() {
+            return mListenedServiceType;
+        }
+
+        @Override
+        public void onServiceFound(@NonNull MdnsServiceInfo serviceInfo) { }
+
+        @Override
+        public void onServiceUpdated(@NonNull MdnsServiceInfo serviceInfo) { }
+
+        @Override
+        public void onServiceRemoved(@NonNull MdnsServiceInfo serviceInfo) { }
+
+        @Override
+        public void onServiceNameDiscovered(@NonNull MdnsServiceInfo serviceInfo) { }
+
+        @Override
+        public void onServiceNameRemoved(@NonNull MdnsServiceInfo serviceInfo) { }
+
+        @Override
+        public void onSearchStoppedWithError(int error) { }
+
+        @Override
+        public void onSearchFailedToStart() { }
+
+        @Override
+        public void onDiscoveryQuerySent(@NonNull List<String> subtypes, int transactionId) { }
+
+        @Override
+        public void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode) { }
+    }
+
+    private class DiscoveryListener extends MdnsListener {
+
+        DiscoveryListener(int clientId, int transactionId, @NonNull NsdServiceInfo reqServiceInfo,
+                @NonNull String listenServiceType) {
+            super(clientId, transactionId, reqServiceInfo, listenServiceType);
+        }
+
+        @Override
+        public void onServiceNameDiscovered(@NonNull MdnsServiceInfo serviceInfo) {
+            mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
+                    NsdManager.SERVICE_FOUND,
+                    new MdnsEvent(mClientId, serviceInfo));
+        }
+
+        @Override
+        public void onServiceNameRemoved(@NonNull MdnsServiceInfo serviceInfo) {
+            mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
+                    NsdManager.SERVICE_LOST,
+                    new MdnsEvent(mClientId, serviceInfo));
+        }
+    }
+
+    private class ResolutionListener extends MdnsListener {
+
+        ResolutionListener(int clientId, int transactionId, @NonNull NsdServiceInfo reqServiceInfo,
+                @NonNull String listenServiceType) {
+            super(clientId, transactionId, reqServiceInfo, listenServiceType);
+        }
+
+        @Override
+        public void onServiceFound(MdnsServiceInfo serviceInfo) {
+            mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
+                    NsdManager.RESOLVE_SERVICE_SUCCEEDED,
+                    new MdnsEvent(mClientId, serviceInfo));
+        }
+    }
+
+    private class ServiceInfoListener extends MdnsListener {
+
+        ServiceInfoListener(int clientId, int transactionId, @NonNull NsdServiceInfo reqServiceInfo,
+                @NonNull String listenServiceType) {
+            super(clientId, transactionId, reqServiceInfo, listenServiceType);
+        }
+
+        @Override
+        public void onServiceFound(@NonNull MdnsServiceInfo serviceInfo) {
+            mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
+                    NsdManager.SERVICE_UPDATED,
+                    new MdnsEvent(mClientId, serviceInfo));
+        }
+
+        @Override
+        public void onServiceUpdated(@NonNull MdnsServiceInfo serviceInfo) {
+            mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
+                    NsdManager.SERVICE_UPDATED,
+                    new MdnsEvent(mClientId, serviceInfo));
+        }
+
+        @Override
+        public void onServiceRemoved(@NonNull MdnsServiceInfo serviceInfo) {
+            mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId,
+                    NsdManager.SERVICE_UPDATED_LOST,
+                    new MdnsEvent(mClientId, serviceInfo));
+        }
+    }
+
+    /**
+     * Data class of mdns service callback information.
+     */
+    private static class MdnsEvent {
+        final int mClientId;
+        @NonNull
+        final MdnsServiceInfo mMdnsServiceInfo;
+
+        MdnsEvent(int clientId, @NonNull MdnsServiceInfo mdnsServiceInfo) {
+            mClientId = clientId;
+            mMdnsServiceInfo = mdnsServiceInfo;
+        }
+    }
 
     private class NsdStateMachine extends StateMachine {
 
@@ -117,6 +332,7 @@
             mMDnsManager.startDaemon();
             mIsDaemonStarted = true;
             maybeScheduleStop();
+            mServiceLogs.log("Start mdns_responder daemon");
         }
 
         private void maybeStopDaemon() {
@@ -127,6 +343,7 @@
             mMDnsManager.unregisterEventListener(mMDnsEventCallback);
             mMDnsManager.stopDaemon();
             mIsDaemonStarted = false;
+            mServiceLogs.log("Stop mdns_responder daemon");
         }
 
         private boolean isAnyRequestActive() {
@@ -148,6 +365,24 @@
             this.removeMessages(NsdManager.DAEMON_CLEANUP);
         }
 
+        private void maybeStartMonitoringSockets() {
+            if (mIsMonitoringSocketsStarted) {
+                if (DBG) Log.d(TAG, "Socket monitoring is already started.");
+                return;
+            }
+
+            mMdnsSocketProvider.startMonitoringSockets();
+            mIsMonitoringSocketsStarted = true;
+        }
+
+        private void maybeStopMonitoringSocketsIfNoActiveRequest() {
+            if (!mIsMonitoringSocketsStarted) return;
+            if (isAnyRequestActive()) return;
+
+            mMdnsSocketProvider.requestStopWhenInactive();
+            mIsMonitoringSocketsStarted = false;
+        }
+
         NsdStateMachine(String name, Handler handler) {
             super(name, handler);
             addState(mDefaultState);
@@ -164,13 +399,14 @@
                 final int clientId = msg.arg2;
                 switch (msg.what) {
                     case NsdManager.REGISTER_CLIENT:
-                        final Pair<NsdServiceConnector, INsdManagerCallback> arg =
-                                (Pair<NsdServiceConnector, INsdManagerCallback>) msg.obj;
-                        final INsdManagerCallback cb = arg.second;
+                        final ConnectorArgs arg = (ConnectorArgs) msg.obj;
+                        final INsdManagerCallback cb = arg.callback;
                         try {
-                            cb.asBinder().linkToDeath(arg.first, 0);
-                            cInfo = new ClientInfo(cb);
-                            mClients.put(arg.first, cInfo);
+                            cb.asBinder().linkToDeath(arg.connector, 0);
+                            final String tag = "Client" + arg.uid + "-" + mClientNumberId++;
+                            cInfo = new ClientInfo(cb, arg.useJavaBackend,
+                                    mServiceLogs.forSubComponent(tag));
+                            mClients.put(arg.connector, cInfo);
                         } catch (RemoteException e) {
                             Log.w(TAG, "Client " + clientId + " has already died");
                         }
@@ -180,10 +416,11 @@
                         cInfo = mClients.remove(connector);
                         if (cInfo != null) {
                             cInfo.expungeAllRequests();
-                            if (cInfo.isLegacy()) {
+                            if (cInfo.isPreSClient()) {
                                 mLegacyClientCount -= 1;
                             }
                         }
+                        maybeStopMonitoringSocketsIfNoActiveRequest();
                         maybeScheduleStop();
                         break;
                     case NsdManager.DISCOVER_SERVICES:
@@ -221,6 +458,20 @@
                                     clientId, NsdManager.FAILURE_INTERNAL_ERROR);
                         }
                         break;
+                    case NsdManager.STOP_RESOLUTION:
+                        cInfo = getClientInfoForReply(msg);
+                        if (cInfo != null) {
+                            cInfo.onStopResolutionFailed(
+                                    clientId, NsdManager.FAILURE_OPERATION_NOT_RUNNING);
+                        }
+                        break;
+                    case NsdManager.REGISTER_SERVICE_CALLBACK:
+                        cInfo = getClientInfoForReply(msg);
+                        if (cInfo != null) {
+                            cInfo.onServiceInfoCallbackRegistrationFailed(
+                                    clientId, NsdManager.FAILURE_BAD_PARAMETERS);
+                        }
+                        break;
                     case NsdManager.DAEMON_CLEANUP:
                         maybeStopDaemon();
                         break;
@@ -230,7 +481,7 @@
                         cInfo = getClientInfoForReply(msg);
                         if (cInfo != null) {
                             cancelStop();
-                            cInfo.setLegacy();
+                            cInfo.setPreSClient();
                             mLegacyClientCount += 1;
                             maybeStartDaemon();
                         }
@@ -262,26 +513,63 @@
             }
 
             private boolean requestLimitReached(ClientInfo clientInfo) {
-                if (clientInfo.mClientIds.size() >= ClientInfo.MAX_LIMIT) {
+                if (clientInfo.mClientRequests.size() >= ClientInfo.MAX_LIMIT) {
                     if (DBG) Log.d(TAG, "Exceeded max outstanding requests " + clientInfo);
                     return true;
                 }
                 return false;
             }
 
-            private void storeRequestMap(int clientId, int globalId, ClientInfo clientInfo, int what) {
-                clientInfo.mClientIds.put(clientId, globalId);
-                clientInfo.mClientRequests.put(clientId, what);
+            private void storeLegacyRequestMap(int clientId, int globalId, ClientInfo clientInfo,
+                    int what) {
+                clientInfo.mClientRequests.put(clientId, new LegacyClientRequest(globalId, what));
                 mIdToClientInfoMap.put(globalId, clientInfo);
                 // Remove the cleanup event because here comes a new request.
                 cancelStop();
             }
 
+            private void storeAdvertiserRequestMap(int clientId, int globalId,
+                    ClientInfo clientInfo) {
+                clientInfo.mClientRequests.put(clientId, new AdvertiserClientRequest(globalId));
+                mIdToClientInfoMap.put(globalId, clientInfo);
+            }
+
             private void removeRequestMap(int clientId, int globalId, ClientInfo clientInfo) {
-                clientInfo.mClientIds.delete(clientId);
-                clientInfo.mClientRequests.delete(clientId);
+                final ClientRequest existing = clientInfo.mClientRequests.get(clientId);
+                if (existing == null) return;
+                clientInfo.mClientRequests.remove(clientId);
                 mIdToClientInfoMap.remove(globalId);
-                maybeScheduleStop();
+
+                if (existing instanceof LegacyClientRequest) {
+                    maybeScheduleStop();
+                } else {
+                    maybeStopMonitoringSocketsIfNoActiveRequest();
+                }
+            }
+
+            private void storeDiscoveryManagerRequestMap(int clientId, int globalId,
+                    MdnsListener listener, ClientInfo clientInfo) {
+                clientInfo.mClientRequests.put(clientId,
+                        new DiscoveryManagerRequest(globalId, listener));
+                mIdToClientInfoMap.put(globalId, clientInfo);
+            }
+
+            /**
+             * Truncate a service name to up to 63 UTF-8 bytes.
+             *
+             * See RFC6763 4.1.1: service instance names are UTF-8 and up to 63 bytes. Truncating
+             * names used in registerService follows historical behavior (see mdnsresponder
+             * handle_regservice_request).
+             */
+            @NonNull
+            private String truncateServiceName(@NonNull String originalName) {
+                return MdnsUtils.truncateServiceName(originalName, MAX_LABEL_LENGTH);
+            }
+
+            private void stopDiscoveryManagerRequest(ClientRequest request, int clientId, int id,
+                    ClientInfo clientInfo) {
+                clientInfo.unregisterMdnsListenerFromRequest(request);
+                removeRequestMap(clientId, id, clientInfo);
             }
 
             @Override
@@ -291,7 +579,7 @@
                 final int clientId = msg.arg2;
                 final ListenerArgs args;
                 switch (msg.what) {
-                    case NsdManager.DISCOVER_SERVICES:
+                    case NsdManager.DISCOVER_SERVICES: {
                         if (DBG) Log.d(TAG, "Discover services");
                         args = (ListenerArgs) msg.obj;
                         clientInfo = mClients.get(args.connector);
@@ -309,22 +597,58 @@
                             break;
                         }
 
-                        maybeStartDaemon();
+                        final NsdServiceInfo info = args.serviceInfo;
                         id = getUniqueId();
-                        if (discoverServices(id, args.serviceInfo)) {
-                            if (DBG) {
-                                Log.d(TAG, "Discover " + msg.arg2 + " " + id
-                                        + args.serviceInfo.getServiceType());
+                        final Pair<String, String> typeAndSubtype =
+                                parseTypeAndSubtype(info.getServiceType());
+                        final String serviceType = typeAndSubtype == null
+                                ? null : typeAndSubtype.first;
+                        if (clientInfo.mUseJavaBackend
+                                || mDeps.isMdnsDiscoveryManagerEnabled(mContext)
+                                || useDiscoveryManagerForType(serviceType)) {
+                            if (serviceType == null) {
+                                clientInfo.onDiscoverServicesFailed(clientId,
+                                        NsdManager.FAILURE_INTERNAL_ERROR);
+                                break;
                             }
-                            storeRequestMap(clientId, id, clientInfo, msg.what);
-                            clientInfo.onDiscoverServicesStarted(clientId, args.serviceInfo);
+
+                            final String listenServiceType = serviceType + ".local";
+                            maybeStartMonitoringSockets();
+                            final MdnsListener listener =
+                                    new DiscoveryListener(clientId, id, info, listenServiceType);
+                            final MdnsSearchOptions.Builder optionsBuilder =
+                                    MdnsSearchOptions.newBuilder()
+                                            .setNetwork(info.getNetwork())
+                                            .setIsPassiveMode(true);
+                            if (typeAndSubtype.second != null) {
+                                // The parsing ensures subtype starts with an underscore.
+                                // MdnsSearchOptions expects the underscore to not be present.
+                                optionsBuilder.addSubtype(typeAndSubtype.second.substring(1));
+                            }
+                            mMdnsDiscoveryManager.registerListener(
+                                    listenServiceType, listener, optionsBuilder.build());
+                            storeDiscoveryManagerRequestMap(clientId, id, listener, clientInfo);
+                            clientInfo.onDiscoverServicesStarted(clientId, info);
+                            clientInfo.log("Register a DiscoveryListener " + id
+                                    + " for service type:" + listenServiceType);
                         } else {
-                            stopServiceDiscovery(id);
-                            clientInfo.onDiscoverServicesFailed(clientId,
-                                    NsdManager.FAILURE_INTERNAL_ERROR);
+                            maybeStartDaemon();
+                            if (discoverServices(id, info)) {
+                                if (DBG) {
+                                    Log.d(TAG, "Discover " + msg.arg2 + " " + id
+                                            + info.getServiceType());
+                                }
+                                storeLegacyRequestMap(clientId, id, clientInfo, msg.what);
+                                clientInfo.onDiscoverServicesStarted(clientId, info);
+                            } else {
+                                stopServiceDiscovery(id);
+                                clientInfo.onDiscoverServicesFailed(clientId,
+                                        NsdManager.FAILURE_INTERNAL_ERROR);
+                            }
                         }
                         break;
-                    case NsdManager.STOP_DISCOVERY:
+                    }
+                    case NsdManager.STOP_DISCOVERY: {
                         if (DBG) Log.d(TAG, "Stop service discovery");
                         args = (ListenerArgs) msg.obj;
                         clientInfo = mClients.get(args.connector);
@@ -336,22 +660,31 @@
                             break;
                         }
 
-                        try {
-                            id = clientInfo.mClientIds.get(clientId);
-                        } catch (NullPointerException e) {
-                            clientInfo.onStopDiscoveryFailed(
-                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                        final ClientRequest request = clientInfo.mClientRequests.get(clientId);
+                        if (request == null) {
+                            Log.e(TAG, "Unknown client request in STOP_DISCOVERY");
                             break;
                         }
-                        removeRequestMap(clientId, id, clientInfo);
-                        if (stopServiceDiscovery(id)) {
+                        id = request.mGlobalId;
+                        // Note isMdnsDiscoveryManagerEnabled may have changed to false at this
+                        // point, so this needs to check the type of the original request to
+                        // unregister instead of looking at the flag value.
+                        if (request instanceof DiscoveryManagerRequest) {
+                            stopDiscoveryManagerRequest(request, clientId, id, clientInfo);
                             clientInfo.onStopDiscoverySucceeded(clientId);
+                            clientInfo.log("Unregister the DiscoveryListener " + id);
                         } else {
-                            clientInfo.onStopDiscoveryFailed(
-                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                            removeRequestMap(clientId, id, clientInfo);
+                            if (stopServiceDiscovery(id)) {
+                                clientInfo.onStopDiscoverySucceeded(clientId);
+                            } else {
+                                clientInfo.onStopDiscoveryFailed(
+                                        clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                            }
                         }
                         break;
-                    case NsdManager.REGISTER_SERVICE:
+                    }
+                    case NsdManager.REGISTER_SERVICE: {
                         if (DBG) Log.d(TAG, "Register service");
                         args = (ListenerArgs) msg.obj;
                         clientInfo = mClients.get(args.connector);
@@ -369,19 +702,48 @@
                             break;
                         }
 
-                        maybeStartDaemon();
                         id = getUniqueId();
-                        if (registerService(id, args.serviceInfo)) {
-                            if (DBG) Log.d(TAG, "Register " + clientId + " " + id);
-                            storeRequestMap(clientId, id, clientInfo, msg.what);
-                            // Return success after mDns reports success
+                        final NsdServiceInfo serviceInfo = args.serviceInfo;
+                        final String serviceType = serviceInfo.getServiceType();
+                        final Pair<String, String> typeSubtype = parseTypeAndSubtype(serviceType);
+                        final String registerServiceType = typeSubtype == null
+                                ? null : typeSubtype.first;
+                        if (clientInfo.mUseJavaBackend
+                                || mDeps.isMdnsAdvertiserEnabled(mContext)
+                                || useAdvertiserForType(registerServiceType)) {
+                            if (registerServiceType == null) {
+                                Log.e(TAG, "Invalid service type: " + serviceType);
+                                clientInfo.onRegisterServiceFailed(clientId,
+                                        NsdManager.FAILURE_INTERNAL_ERROR);
+                                break;
+                            }
+                            serviceInfo.setServiceType(registerServiceType);
+                            serviceInfo.setServiceName(truncateServiceName(
+                                    serviceInfo.getServiceName()));
+
+                            maybeStartMonitoringSockets();
+                            // TODO: pass in the subtype as well. Including the subtype in the
+                            // service type would generate service instance names like
+                            // Name._subtype._sub._type._tcp, which is incorrect
+                            // (it should be Name._type._tcp).
+                            mAdvertiser.addService(id, serviceInfo, typeSubtype.second);
+                            storeAdvertiserRequestMap(clientId, id, clientInfo);
                         } else {
-                            unregisterService(id);
-                            clientInfo.onRegisterServiceFailed(
-                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                            maybeStartDaemon();
+                            if (registerService(id, serviceInfo)) {
+                                if (DBG) Log.d(TAG, "Register " + clientId + " " + id);
+                                storeLegacyRequestMap(clientId, id, clientInfo, msg.what);
+                                // Return success after mDns reports success
+                            } else {
+                                unregisterService(id);
+                                clientInfo.onRegisterServiceFailed(
+                                        clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                            }
+
                         }
                         break;
-                    case NsdManager.UNREGISTER_SERVICE:
+                    }
+                    case NsdManager.UNREGISTER_SERVICE: {
                         if (DBG) Log.d(TAG, "unregister service");
                         args = (ListenerArgs) msg.obj;
                         clientInfo = mClients.get(args.connector);
@@ -392,16 +754,31 @@
                             Log.e(TAG, "Unknown connector in unregistration");
                             break;
                         }
-                        id = clientInfo.mClientIds.get(clientId);
+                        final ClientRequest request = clientInfo.mClientRequests.get(clientId);
+                        if (request == null) {
+                            Log.e(TAG, "Unknown client request in UNREGISTER_SERVICE");
+                            break;
+                        }
+                        id = request.mGlobalId;
                         removeRequestMap(clientId, id, clientInfo);
-                        if (unregisterService(id)) {
+
+                        // Note isMdnsAdvertiserEnabled may have changed to false at this point,
+                        // so this needs to check the type of the original request to unregister
+                        // instead of looking at the flag value.
+                        if (request instanceof AdvertiserClientRequest) {
+                            mAdvertiser.removeService(id);
                             clientInfo.onUnregisterServiceSucceeded(clientId);
                         } else {
-                            clientInfo.onUnregisterServiceFailed(
-                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                            if (unregisterService(id)) {
+                                clientInfo.onUnregisterServiceSucceeded(clientId);
+                            } else {
+                                clientInfo.onUnregisterServiceFailed(
+                                        clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                            }
                         }
                         break;
-                    case NsdManager.RESOLVE_SERVICE:
+                    }
+                    case NsdManager.RESOLVE_SERVICE: {
                         if (DBG) Log.d(TAG, "Resolve service");
                         args = (ListenerArgs) msg.obj;
                         clientInfo = mClients.get(args.connector);
@@ -413,27 +790,167 @@
                             break;
                         }
 
-                        if (clientInfo.mResolvedService != null) {
-                            clientInfo.onResolveServiceFailed(
-                                    clientId, NsdManager.FAILURE_ALREADY_ACTIVE);
+                        final NsdServiceInfo info = args.serviceInfo;
+                        id = getUniqueId();
+                        final Pair<String, String> typeSubtype =
+                                parseTypeAndSubtype(info.getServiceType());
+                        final String serviceType = typeSubtype == null
+                                ? null : typeSubtype.first;
+                        if (clientInfo.mUseJavaBackend
+                                ||  mDeps.isMdnsDiscoveryManagerEnabled(mContext)
+                                || useDiscoveryManagerForType(serviceType)) {
+                            if (serviceType == null) {
+                                clientInfo.onResolveServiceFailed(clientId,
+                                        NsdManager.FAILURE_INTERNAL_ERROR);
+                                break;
+                            }
+                            final String resolveServiceType = serviceType + ".local";
+
+                            maybeStartMonitoringSockets();
+                            final MdnsListener listener =
+                                    new ResolutionListener(clientId, id, info, resolveServiceType);
+                            final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
+                                    .setNetwork(info.getNetwork())
+                                    .setIsPassiveMode(true)
+                                    .setResolveInstanceName(info.getServiceName())
+                                    .build();
+                            mMdnsDiscoveryManager.registerListener(
+                                    resolveServiceType, listener, options);
+                            storeDiscoveryManagerRequestMap(clientId, id, listener, clientInfo);
+                            clientInfo.log("Register a ResolutionListener " + id
+                                    + " for service type:" + resolveServiceType);
+                        } else {
+                            if (clientInfo.mResolvedService != null) {
+                                clientInfo.onResolveServiceFailed(
+                                        clientId, NsdManager.FAILURE_ALREADY_ACTIVE);
+                                break;
+                            }
+
+                            maybeStartDaemon();
+                            if (resolveService(id, info)) {
+                                clientInfo.mResolvedService = new NsdServiceInfo();
+                                storeLegacyRequestMap(clientId, id, clientInfo, msg.what);
+                            } else {
+                                clientInfo.onResolveServiceFailed(
+                                        clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                            }
+                        }
+                        break;
+                    }
+                    case NsdManager.STOP_RESOLUTION: {
+                        if (DBG) Log.d(TAG, "Stop service resolution");
+                        args = (ListenerArgs) msg.obj;
+                        clientInfo = mClients.get(args.connector);
+                        // If the binder death notification for a INsdManagerCallback was received
+                        // before any calls are received by NsdService, the clientInfo would be
+                        // cleared and cause NPE. Add a null check here to prevent this corner case.
+                        if (clientInfo == null) {
+                            Log.e(TAG, "Unknown connector in stop resolution");
                             break;
                         }
 
-                        maybeStartDaemon();
-                        id = getUniqueId();
-                        if (resolveService(id, args.serviceInfo)) {
-                            clientInfo.mResolvedService = new NsdServiceInfo();
-                            storeRequestMap(clientId, id, clientInfo, msg.what);
+                        final ClientRequest request = clientInfo.mClientRequests.get(clientId);
+                        if (request == null) {
+                            Log.e(TAG, "Unknown client request in STOP_RESOLUTION");
+                            break;
+                        }
+                        id = request.mGlobalId;
+                        // Note isMdnsDiscoveryManagerEnabled may have changed to false at this
+                        // point, so this needs to check the type of the original request to
+                        // unregister instead of looking at the flag value.
+                        if (request instanceof DiscoveryManagerRequest) {
+                            stopDiscoveryManagerRequest(request, clientId, id, clientInfo);
+                            clientInfo.onStopResolutionSucceeded(clientId);
+                            clientInfo.log("Unregister the ResolutionListener " + id);
                         } else {
-                            clientInfo.onResolveServiceFailed(
-                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                            removeRequestMap(clientId, id, clientInfo);
+                            if (stopResolveService(id)) {
+                                clientInfo.onStopResolutionSucceeded(clientId);
+                            } else {
+                                clientInfo.onStopResolutionFailed(
+                                        clientId, NsdManager.FAILURE_OPERATION_NOT_RUNNING);
+                            }
+                            clientInfo.mResolvedService = null;
                         }
                         break;
+                    }
+                    case NsdManager.REGISTER_SERVICE_CALLBACK: {
+                        if (DBG) Log.d(TAG, "Register a service callback");
+                        args = (ListenerArgs) msg.obj;
+                        clientInfo = mClients.get(args.connector);
+                        // If the binder death notification for a INsdManagerCallback was received
+                        // before any calls are received by NsdService, the clientInfo would be
+                        // cleared and cause NPE. Add a null check here to prevent this corner case.
+                        if (clientInfo == null) {
+                            Log.e(TAG, "Unknown connector in callback registration");
+                            break;
+                        }
+
+                        final NsdServiceInfo info = args.serviceInfo;
+                        id = getUniqueId();
+                        final Pair<String, String> typeAndSubtype =
+                                parseTypeAndSubtype(info.getServiceType());
+                        final String serviceType = typeAndSubtype == null
+                                ? null : typeAndSubtype.first;
+                        if (serviceType == null) {
+                            clientInfo.onServiceInfoCallbackRegistrationFailed(clientId,
+                                    NsdManager.FAILURE_BAD_PARAMETERS);
+                            break;
+                        }
+                        final String resolveServiceType = serviceType + ".local";
+
+                        maybeStartMonitoringSockets();
+                        final MdnsListener listener =
+                                new ServiceInfoListener(clientId, id, info, resolveServiceType);
+                        final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
+                                .setNetwork(info.getNetwork())
+                                .setIsPassiveMode(true)
+                                .setResolveInstanceName(info.getServiceName())
+                                .build();
+                        mMdnsDiscoveryManager.registerListener(
+                                resolveServiceType, listener, options);
+                        storeDiscoveryManagerRequestMap(clientId, id, listener, clientInfo);
+                        clientInfo.log("Register a ServiceInfoListener " + id
+                                + " for service type:" + resolveServiceType);
+                        break;
+                    }
+                    case NsdManager.UNREGISTER_SERVICE_CALLBACK: {
+                        if (DBG) Log.d(TAG, "Unregister a service callback");
+                        args = (ListenerArgs) msg.obj;
+                        clientInfo = mClients.get(args.connector);
+                        // If the binder death notification for a INsdManagerCallback was received
+                        // before any calls are received by NsdService, the clientInfo would be
+                        // cleared and cause NPE. Add a null check here to prevent this corner case.
+                        if (clientInfo == null) {
+                            Log.e(TAG, "Unknown connector in callback unregistration");
+                            break;
+                        }
+
+                        final ClientRequest request = clientInfo.mClientRequests.get(clientId);
+                        if (request == null) {
+                            Log.e(TAG, "Unknown client request in UNREGISTER_SERVICE_CALLBACK");
+                            break;
+                        }
+                        id = request.mGlobalId;
+                        if (request instanceof DiscoveryManagerRequest) {
+                            stopDiscoveryManagerRequest(request, clientId, id, clientInfo);
+                            clientInfo.onServiceInfoCallbackUnregistered(clientId);
+                            clientInfo.log("Unregister the ServiceInfoListener " + id);
+                        } else {
+                            loge("Unregister failed with non-DiscoveryManagerRequest.");
+                        }
+                        break;
+                    }
                     case MDNS_SERVICE_EVENT:
                         if (!handleMDnsServiceEvent(msg.arg1, msg.arg2, msg.obj)) {
                             return NOT_HANDLED;
                         }
                         break;
+                    case MDNS_DISCOVERY_MANAGER_EVENT:
+                        if (!handleMdnsDiscoveryManagerEvent(msg.arg1, msg.arg2, msg.obj)) {
+                            return NOT_HANDLED;
+                        }
+                        break;
                     default:
                         return NOT_HANDLED;
                 }
@@ -474,6 +991,12 @@
                             // interfaces that do not have an associated Network.
                             break;
                         }
+                        if (foundNetId == INetd.DUMMY_NET_ID) {
+                            // Ignore services on the dummy0 interface: they are only seen when
+                            // discovering locally advertised services, and are not reachable
+                            // through that interface.
+                            break;
+                        }
                         setServiceNetworkForCallback(servInfo, info.netId, info.interfaceIdx);
                         clientInfo.onServiceFound(clientId, servInfo);
                         break;
@@ -526,10 +1049,11 @@
                         String rest = fullName.substring(index);
                         String type = rest.replace(".local.", "");
 
-                        clientInfo.mResolvedService.setServiceName(name);
-                        clientInfo.mResolvedService.setServiceType(type);
-                        clientInfo.mResolvedService.setPort(info.port);
-                        clientInfo.mResolvedService.setTxtRecords(info.txtRecord);
+                        final NsdServiceInfo serviceInfo = clientInfo.mResolvedService;
+                        serviceInfo.setServiceName(name);
+                        serviceInfo.setServiceType(type);
+                        serviceInfo.setPort(info.port);
+                        serviceInfo.setTxtRecords(info.txtRecord);
                         // Network will be added after SERVICE_GET_ADDR_SUCCESS
 
                         stopResolveService(id);
@@ -537,7 +1061,8 @@
 
                         final int id2 = getUniqueId();
                         if (getAddrInfo(id2, info.hostname, info.interfaceIdx)) {
-                            storeRequestMap(clientId, id2, clientInfo, NsdManager.RESOLVE_SERVICE);
+                            storeLegacyRequestMap(clientId, id2, clientInfo,
+                                    NsdManager.RESOLVE_SERVICE);
                         } else {
                             clientInfo.onResolveServiceFailed(
                                     clientId, NsdManager.FAILURE_INTERNAL_ERROR);
@@ -549,17 +1074,17 @@
                         /* NNN resolveId errorCode */
                         stopResolveService(id);
                         removeRequestMap(clientId, id, clientInfo);
-                        clientInfo.mResolvedService = null;
                         clientInfo.onResolveServiceFailed(
                                 clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                        clientInfo.mResolvedService = null;
                         break;
                     case IMDnsEventListener.SERVICE_GET_ADDR_FAILED:
                         /* NNN resolveId errorCode */
                         stopGetAddrInfo(id);
                         removeRequestMap(clientId, id, clientInfo);
-                        clientInfo.mResolvedService = null;
                         clientInfo.onResolveServiceFailed(
                                 clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                        clientInfo.mResolvedService = null;
                         break;
                     case IMDnsEventListener.SERVICE_GET_ADDR_SUCCESS: {
                         /* NNN resolveId hostname ttl addr interfaceIdx netId */
@@ -596,9 +1121,167 @@
                 }
                 return true;
             }
+
+            @Nullable
+            private NsdServiceInfo buildNsdServiceInfoFromMdnsEvent(
+                    final MdnsEvent event, int code) {
+                final MdnsServiceInfo serviceInfo = event.mMdnsServiceInfo;
+                final String[] typeArray = serviceInfo.getServiceType();
+                final String joinedType;
+                if (typeArray.length == 0
+                        || !typeArray[typeArray.length - 1].equals(LOCAL_DOMAIN_NAME)) {
+                    Log.wtf(TAG, "MdnsServiceInfo type does not end in .local: "
+                            + Arrays.toString(typeArray));
+                    return null;
+                } else {
+                    joinedType = TextUtils.join(".",
+                            Arrays.copyOfRange(typeArray, 0, typeArray.length - 1));
+                }
+                final String serviceType;
+                switch (code) {
+                    case NsdManager.SERVICE_FOUND:
+                    case NsdManager.SERVICE_LOST:
+                        // For consistency with historical behavior, discovered service types have
+                        // a dot at the end.
+                        serviceType = joinedType + ".";
+                        break;
+                    case RESOLVE_SERVICE_SUCCEEDED:
+                        // For consistency with historical behavior, resolved service types have
+                        // a dot at the beginning.
+                        serviceType = "." + joinedType;
+                        break;
+                    default:
+                        serviceType = joinedType;
+                        break;
+                }
+                final String serviceName = serviceInfo.getServiceInstanceName();
+                final NsdServiceInfo servInfo = new NsdServiceInfo(serviceName, serviceType);
+                final Network network = serviceInfo.getNetwork();
+                // In MdnsDiscoveryManagerEvent, the Network can be null which means it is a
+                // network for Tethering interface. In other words, the network == null means the
+                // network has netId = INetd.LOCAL_NET_ID.
+                setServiceNetworkForCallback(
+                        servInfo,
+                        network == null ? INetd.LOCAL_NET_ID : network.netId,
+                        serviceInfo.getInterfaceIndex());
+                return servInfo;
+            }
+
+            private boolean handleMdnsDiscoveryManagerEvent(
+                    int transactionId, int code, Object obj) {
+                final ClientInfo clientInfo = mIdToClientInfoMap.get(transactionId);
+                if (clientInfo == null) {
+                    Log.e(TAG, String.format(
+                            "id %d for %d has no client mapping", transactionId, code));
+                    return false;
+                }
+
+                final MdnsEvent event = (MdnsEvent) obj;
+                final int clientId = event.mClientId;
+                final NsdServiceInfo info = buildNsdServiceInfoFromMdnsEvent(event, code);
+                // Errors are already logged if null
+                if (info == null) return false;
+                if (DBG) {
+                    Log.d(TAG, String.format("MdnsDiscoveryManager event code=%s transactionId=%d",
+                            NsdManager.nameOf(code), transactionId));
+                }
+                switch (code) {
+                    case NsdManager.SERVICE_FOUND:
+                        clientInfo.onServiceFound(clientId, info);
+                        break;
+                    case NsdManager.SERVICE_LOST:
+                        clientInfo.onServiceLost(clientId, info);
+                        break;
+                    case NsdManager.RESOLVE_SERVICE_SUCCEEDED: {
+                        final ClientRequest request = clientInfo.mClientRequests.get(clientId);
+                        if (request == null) {
+                            Log.e(TAG, "Unknown client request in RESOLVE_SERVICE_SUCCEEDED");
+                            break;
+                        }
+                        final MdnsServiceInfo serviceInfo = event.mMdnsServiceInfo;
+                        info.setPort(serviceInfo.getPort());
+
+                        Map<String, String> attrs = serviceInfo.getAttributes();
+                        for (Map.Entry<String, String> kv : attrs.entrySet()) {
+                            final String key = kv.getKey();
+                            try {
+                                info.setAttribute(key, serviceInfo.getAttributeAsBytes(key));
+                            } catch (IllegalArgumentException e) {
+                                Log.e(TAG, "Invalid attribute", e);
+                            }
+                        }
+                        final List<InetAddress> addresses = getInetAddresses(serviceInfo);
+                        if (addresses.size() != 0) {
+                            info.setHostAddresses(addresses);
+                            clientInfo.onResolveServiceSucceeded(clientId, info);
+                        } else {
+                            // No address. Notify resolution failure.
+                            clientInfo.onResolveServiceFailed(
+                                    clientId, NsdManager.FAILURE_INTERNAL_ERROR);
+                        }
+
+                        // Unregister the listener immediately like IMDnsEventListener design
+                        if (!(request instanceof DiscoveryManagerRequest)) {
+                            Log.wtf(TAG, "non-DiscoveryManager request in DiscoveryManager event");
+                            break;
+                        }
+                        stopDiscoveryManagerRequest(request, clientId, transactionId, clientInfo);
+                        break;
+                    }
+                    case NsdManager.SERVICE_UPDATED: {
+                        final MdnsServiceInfo serviceInfo = event.mMdnsServiceInfo;
+                        info.setPort(serviceInfo.getPort());
+
+                        Map<String, String> attrs = serviceInfo.getAttributes();
+                        for (Map.Entry<String, String> kv : attrs.entrySet()) {
+                            final String key = kv.getKey();
+                            try {
+                                info.setAttribute(key, serviceInfo.getAttributeAsBytes(key));
+                            } catch (IllegalArgumentException e) {
+                                Log.e(TAG, "Invalid attribute", e);
+                            }
+                        }
+
+                        final List<InetAddress> addresses = getInetAddresses(serviceInfo);
+                        info.setHostAddresses(addresses);
+                        clientInfo.onServiceUpdated(clientId, info);
+                        break;
+                    }
+                    case NsdManager.SERVICE_UPDATED_LOST:
+                        clientInfo.onServiceUpdatedLost(clientId);
+                        break;
+                    default:
+                        return false;
+                }
+                return true;
+            }
        }
     }
 
+    @NonNull
+    private static List<InetAddress> getInetAddresses(@NonNull MdnsServiceInfo serviceInfo) {
+        final List<String> v4Addrs = serviceInfo.getIpv4Addresses();
+        final List<String> v6Addrs = serviceInfo.getIpv6Addresses();
+        final List<InetAddress> addresses = new ArrayList<>(v4Addrs.size() + v6Addrs.size());
+        for (String ipv4Address : v4Addrs) {
+            try {
+                addresses.add(InetAddresses.parseNumericAddress(ipv4Address));
+            } catch (IllegalArgumentException e) {
+                Log.wtf(TAG, "Invalid ipv4 address", e);
+            }
+        }
+        for (String ipv6Address : v6Addrs) {
+            try {
+                final Inet6Address addr = (Inet6Address) InetAddresses.parseNumericAddress(
+                        ipv6Address);
+                addresses.add(InetAddressUtils.withScopeId(addr, serviceInfo.getInterfaceIndex()));
+            } catch (IllegalArgumentException e) {
+                Log.wtf(TAG, "Invalid ipv6 address", e);
+            }
+        }
+        return addresses;
+    }
+
     private static void setServiceNetworkForCallback(NsdServiceInfo info, int netId, int ifaceIdx) {
         switch (netId) {
             case NETID_UNSET:
@@ -648,14 +1331,181 @@
         return sb.toString();
     }
 
+    /**
+     * Check the given service type is valid and construct it to a service type
+     * which can use for discovery / resolution service.
+     *
+     * <p>The valid service type should be 2 labels, or 3 labels if the query is for a
+     * subtype (see RFC6763 7.1). Each label is up to 63 characters and must start with an
+     * underscore; they are alphanumerical characters or dashes or underscore, except the
+     * last one that is just alphanumerical. The last label must be _tcp or _udp.
+     *
+     * <p>The subtype may also be specified with a comma after the service type, for example
+     * _type._tcp,_subtype.
+     *
+     * @param serviceType the request service type for discovery / resolution service
+     * @return constructed service type or null if the given service type is invalid.
+     */
+    @Nullable
+    public static Pair<String, String> parseTypeAndSubtype(String serviceType) {
+        if (TextUtils.isEmpty(serviceType)) return null;
+
+        final String typeOrSubtypePattern = "_[a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]";
+        final Pattern serviceTypePattern = Pattern.compile(
+                // Optional leading subtype (_subtype._type._tcp)
+                // (?: xxx) is a non-capturing parenthesis, don't capture the dot
+                "^(?:(" + typeOrSubtypePattern + ")\\.)?"
+                        // Actual type (_type._tcp.local)
+                        + "(" + typeOrSubtypePattern + "\\._(?:tcp|udp))"
+                        // Drop '.' at the end of service type that is compatible with old backend.
+                        // e.g. allow "_type._tcp.local."
+                        + "\\.?"
+                        // Optional subtype after comma, for "_type._tcp,_subtype" format
+                        + "(?:,(" + typeOrSubtypePattern + "))?"
+                        + "$");
+        final Matcher matcher = serviceTypePattern.matcher(serviceType);
+        if (!matcher.matches()) return null;
+        // Use the subtype either at the beginning or after the comma
+        final String subtype = matcher.group(1) != null ? matcher.group(1) : matcher.group(3);
+        return new Pair<>(matcher.group(2), subtype);
+    }
+
     @VisibleForTesting
     NsdService(Context ctx, Handler handler, long cleanupDelayMs) {
+        this(ctx, handler, cleanupDelayMs, new Dependencies());
+    }
+
+    @VisibleForTesting
+    NsdService(Context ctx, Handler handler, long cleanupDelayMs, Dependencies deps) {
         mCleanupDelayMs = cleanupDelayMs;
         mContext = ctx;
         mNsdStateMachine = new NsdStateMachine(TAG, handler);
         mNsdStateMachine.start();
         mMDnsManager = ctx.getSystemService(MDnsManager.class);
         mMDnsEventCallback = new MDnsEventCallback(mNsdStateMachine);
+        mDeps = deps;
+
+        mMdnsSocketProvider = deps.makeMdnsSocketProvider(ctx, handler.getLooper(),
+                LOGGER.forSubComponent("MdnsSocketProvider"));
+        // Netlink monitor starts on boot, and intentionally never stopped, to ensure that all
+        // address events are received.
+        handler.post(mMdnsSocketProvider::startNetLinkMonitor);
+        mMdnsSocketClient =
+                new MdnsMultinetworkSocketClient(handler.getLooper(), mMdnsSocketProvider);
+        mMdnsDiscoveryManager = deps.makeMdnsDiscoveryManager(new ExecutorProvider(),
+                mMdnsSocketClient, LOGGER.forSubComponent("MdnsDiscoveryManager"));
+        handler.post(() -> mMdnsSocketClient.setCallback(mMdnsDiscoveryManager));
+        mAdvertiser = deps.makeMdnsAdvertiser(handler.getLooper(), mMdnsSocketProvider,
+                new AdvertiserCallback(), LOGGER.forSubComponent("MdnsAdvertiser"));
+    }
+
+    /**
+     * Dependencies of NsdService, for injection in tests.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        /**
+         * Check whether the MdnsDiscoveryManager feature is enabled.
+         *
+         * @param context The global context information about an app environment.
+         * @return true if the MdnsDiscoveryManager feature is enabled.
+         */
+        public boolean isMdnsDiscoveryManagerEnabled(Context context) {
+            return isAtLeastU() || DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_TETHERING,
+                    MDNS_DISCOVERY_MANAGER_VERSION, DeviceConfigUtils.TETHERING_MODULE_NAME,
+                    false /* defaultEnabled */);
+        }
+
+        /**
+         * Check whether the MdnsAdvertiser feature is enabled.
+         *
+         * @param context The global context information about an app environment.
+         * @return true if the MdnsAdvertiser feature is enabled.
+         */
+        public boolean isMdnsAdvertiserEnabled(Context context) {
+            return isAtLeastU() || DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_TETHERING,
+                    MDNS_ADVERTISER_VERSION, DeviceConfigUtils.TETHERING_MODULE_NAME,
+                    false /* defaultEnabled */);
+        }
+
+        /**
+         * Get the type allowlist flag value.
+         * @see #MDNS_TYPE_ALLOWLIST_FLAGS
+         */
+        @Nullable
+        public String getTypeAllowlistFlags() {
+            return DeviceConfigUtils.getDeviceConfigProperty(NAMESPACE_TETHERING,
+                    MDNS_TYPE_ALLOWLIST_FLAGS, null);
+        }
+
+        /**
+         * @see DeviceConfigUtils#isFeatureEnabled(Context, String, String, String, boolean)
+         */
+        public boolean isFeatureEnabled(Context context, String feature) {
+            return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_TETHERING,
+                    feature, DeviceConfigUtils.TETHERING_MODULE_NAME, false /* defaultEnabled */);
+        }
+
+        /**
+         * @see MdnsDiscoveryManager
+         */
+        public MdnsDiscoveryManager makeMdnsDiscoveryManager(
+                @NonNull ExecutorProvider executorProvider,
+                @NonNull MdnsMultinetworkSocketClient socketClient, @NonNull SharedLog sharedLog) {
+            return new MdnsDiscoveryManager(executorProvider, socketClient, sharedLog);
+        }
+
+        /**
+         * @see MdnsAdvertiser
+         */
+        public MdnsAdvertiser makeMdnsAdvertiser(
+                @NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
+                @NonNull MdnsAdvertiser.AdvertiserCallback cb, @NonNull SharedLog sharedLog) {
+            return new MdnsAdvertiser(looper, socketProvider, cb, sharedLog);
+        }
+
+        /**
+         * @see MdnsSocketProvider
+         */
+        public MdnsSocketProvider makeMdnsSocketProvider(@NonNull Context context,
+                @NonNull Looper looper, @NonNull SharedLog sharedLog) {
+            return new MdnsSocketProvider(context, looper, sharedLog);
+        }
+    }
+
+    /**
+     * Return whether a type is allowlisted to use the Java backend.
+     * @param type The service type
+     * @param flagPrefix One of {@link #MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX} or
+     *                   {@link #MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX}.
+     */
+    private boolean isTypeAllowlistedForJavaBackend(@Nullable String type,
+            @NonNull String flagPrefix) {
+        if (type == null) return false;
+        final String typesConfig = mDeps.getTypeAllowlistFlags();
+        if (TextUtils.isEmpty(typesConfig)) return false;
+
+        final String mappingPrefix = type + ":";
+        String mappedFlag = null;
+        for (String mapping : TextUtils.split(typesConfig, ",")) {
+            if (mapping.startsWith(mappingPrefix)) {
+                mappedFlag = mapping.substring(mappingPrefix.length());
+                break;
+            }
+        }
+
+        if (mappedFlag == null) return false;
+
+        return mDeps.isFeatureEnabled(mContext,
+                flagPrefix + mappedFlag + MDNS_ALLOWLIST_FLAG_SUFFIX);
+    }
+
+    private boolean useDiscoveryManagerForType(@Nullable String type) {
+        return isTypeAllowlistedForJavaBackend(type, MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX);
+    }
+
+    private boolean useAdvertiserForType(@Nullable String type) {
+        return isTypeAllowlistedForJavaBackend(type, MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX);
     }
 
     public static NsdService create(Context context) {
@@ -708,12 +1558,72 @@
         }
     }
 
+    private class AdvertiserCallback implements MdnsAdvertiser.AdvertiserCallback {
+        @Override
+        public void onRegisterServiceSucceeded(int serviceId, NsdServiceInfo registeredInfo) {
+            final ClientInfo clientInfo = getClientInfoOrLog(serviceId);
+            if (clientInfo == null) return;
+
+            final int clientId = getClientIdOrLog(clientInfo, serviceId);
+            if (clientId < 0) return;
+
+            // onRegisterServiceSucceeded only has the service name in its info. This aligns with
+            // historical behavior.
+            final NsdServiceInfo cbInfo = new NsdServiceInfo(registeredInfo.getServiceName(), null);
+            clientInfo.onRegisterServiceSucceeded(clientId, cbInfo);
+        }
+
+        @Override
+        public void onRegisterServiceFailed(int serviceId, int errorCode) {
+            final ClientInfo clientInfo = getClientInfoOrLog(serviceId);
+            if (clientInfo == null) return;
+
+            final int clientId = getClientIdOrLog(clientInfo, serviceId);
+            if (clientId < 0) return;
+
+            clientInfo.onRegisterServiceFailed(clientId, errorCode);
+        }
+
+        private ClientInfo getClientInfoOrLog(int serviceId) {
+            final ClientInfo clientInfo = mIdToClientInfoMap.get(serviceId);
+            if (clientInfo == null) {
+                Log.e(TAG, String.format("Callback for service %d has no client", serviceId));
+            }
+            return clientInfo;
+        }
+
+        private int getClientIdOrLog(@NonNull ClientInfo info, int serviceId) {
+            final int clientId = info.getClientId(serviceId);
+            if (clientId < 0) {
+                Log.e(TAG, String.format("Client ID not found for service %d", serviceId));
+            }
+            return clientId;
+        }
+    }
+
+    private static class ConnectorArgs {
+        @NonNull public final NsdServiceConnector connector;
+        @NonNull public final INsdManagerCallback callback;
+        public final boolean useJavaBackend;
+        public final int uid;
+
+        ConnectorArgs(@NonNull NsdServiceConnector connector, @NonNull INsdManagerCallback callback,
+                boolean useJavaBackend, int uid) {
+            this.connector = connector;
+            this.callback = callback;
+            this.useJavaBackend = useJavaBackend;
+            this.uid = uid;
+        }
+    }
+
     @Override
-    public INsdServiceConnector connect(INsdManagerCallback cb) {
+    public INsdServiceConnector connect(INsdManagerCallback cb, boolean useJavaBackend) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "NsdService");
+        if (DBG) Log.d(TAG, "New client connect. useJavaBackend=" + useJavaBackend);
         final INsdServiceConnector connector = new NsdServiceConnector();
-        mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
-                NsdManager.REGISTER_CLIENT, new Pair<>(connector, cb)));
+        mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(NsdManager.REGISTER_CLIENT,
+                new ConnectorArgs((NsdServiceConnector) connector, cb, useJavaBackend,
+                        Binder.getCallingUid())));
         return connector;
     }
 
@@ -763,6 +1673,26 @@
         }
 
         @Override
+        public void stopResolution(int listenerKey) {
+            mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+                    NsdManager.STOP_RESOLUTION, 0, listenerKey, new ListenerArgs(this, null)));
+        }
+
+        @Override
+        public void registerServiceInfoCallback(int listenerKey, NsdServiceInfo serviceInfo) {
+            mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+                    NsdManager.REGISTER_SERVICE_CALLBACK, 0, listenerKey,
+                    new ListenerArgs(this, serviceInfo)));
+        }
+
+        @Override
+        public void unregisterServiceInfoCallback(int listenerKey) {
+            mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
+                    NsdManager.UNREGISTER_SERVICE_CALLBACK, 0, listenerKey,
+                    new ListenerArgs(this, null)));
+        }
+
+        @Override
         public void startDaemon() {
             mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(
                     NsdManager.DAEMON_STARTUP, new ListenerArgs(this, null)));
@@ -892,15 +1822,52 @@
     }
 
     @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!PermissionUtils.checkDumpPermission(mContext, TAG, pw)) return;
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        if (!PermissionUtils.checkDumpPermission(mContext, TAG, writer)) return;
 
-        for (ClientInfo client : mClients.values()) {
-            pw.println("Client Info");
-            pw.println(client);
-        }
-
+        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+        // Dump state machine logs
         mNsdStateMachine.dump(fd, pw, args);
+
+        // Dump service and clients logs
+        pw.println();
+        pw.println("Logs:");
+        pw.increaseIndent();
+        mServiceLogs.reverseDump(pw);
+        pw.decreaseIndent();
+    }
+
+    private abstract static class ClientRequest {
+        private final int mGlobalId;
+
+        private ClientRequest(int globalId) {
+            mGlobalId = globalId;
+        }
+    }
+
+    private static class LegacyClientRequest extends ClientRequest {
+        private final int mRequestCode;
+
+        private LegacyClientRequest(int globalId, int requestCode) {
+            super(globalId);
+            mRequestCode = requestCode;
+        }
+    }
+
+    private static class AdvertiserClientRequest extends ClientRequest {
+        private AdvertiserClientRequest(int globalId) {
+            super(globalId);
+        }
+    }
+
+    private static class DiscoveryManagerRequest extends ClientRequest {
+        @NonNull
+        private final MdnsListener mListener;
+
+        private DiscoveryManagerRequest(int globalId, @NonNull MdnsListener listener) {
+            super(globalId);
+            mListener = listener;
+        }
     }
 
     /* Information tracked per client */
@@ -911,56 +1878,85 @@
         /* Remembers a resolved service until getaddrinfo completes */
         private NsdServiceInfo mResolvedService;
 
-        /* A map from client id to unique id sent to mDns */
-        private final SparseIntArray mClientIds = new SparseIntArray();
-
-        /* A map from client id to the type of the request we had received */
-        private final SparseIntArray mClientRequests = new SparseIntArray();
+        /* A map from client-side ID (listenerKey) to the request */
+        private final SparseArray<ClientRequest> mClientRequests = new SparseArray<>();
 
         // The target SDK of this client < Build.VERSION_CODES.S
-        private boolean mIsLegacy = false;
+        private boolean mIsPreSClient = false;
+        // The flag of using java backend if the client's target SDK >= U
+        private final boolean mUseJavaBackend;
+        // Store client logs
+        private final SharedLog mClientLogs;
 
-        private ClientInfo(INsdManagerCallback cb) {
+        private ClientInfo(INsdManagerCallback cb, boolean useJavaBackend, SharedLog sharedLog) {
             mCb = cb;
-            if (DBG) Log.d(TAG, "New client");
+            mUseJavaBackend = useJavaBackend;
+            mClientLogs = sharedLog;
+            mClientLogs.log("New client. useJavaBackend=" + useJavaBackend);
         }
 
         @Override
         public String toString() {
             StringBuilder sb = new StringBuilder();
             sb.append("mResolvedService ").append(mResolvedService).append("\n");
-            sb.append("mIsLegacy ").append(mIsLegacy).append("\n");
-            for(int i = 0; i< mClientIds.size(); i++) {
-                int clientID = mClientIds.keyAt(i);
-                sb.append("clientId ").append(clientID).
-                    append(" mDnsId ").append(mClientIds.valueAt(i)).
-                    append(" type ").append(mClientRequests.get(clientID)).append("\n");
+            sb.append("mIsLegacy ").append(mIsPreSClient).append("\n");
+            for (int i = 0; i < mClientRequests.size(); i++) {
+                int clientID = mClientRequests.keyAt(i);
+                sb.append("clientId ")
+                        .append(clientID)
+                        .append(" mDnsId ").append(mClientRequests.valueAt(i).mGlobalId)
+                        .append(" type ").append(
+                                mClientRequests.valueAt(i).getClass().getSimpleName())
+                        .append("\n");
             }
             return sb.toString();
         }
 
-        private boolean isLegacy() {
-            return mIsLegacy;
+        private boolean isPreSClient() {
+            return mIsPreSClient;
         }
 
-        private void setLegacy() {
-            mIsLegacy = true;
+        private void setPreSClient() {
+            mIsPreSClient = true;
+        }
+
+        private void unregisterMdnsListenerFromRequest(ClientRequest request) {
+            final MdnsListener listener =
+                    ((DiscoveryManagerRequest) request).mListener;
+            mMdnsDiscoveryManager.unregisterListener(
+                    listener.getListenedServiceType(), listener);
         }
 
         // Remove any pending requests from the global map when we get rid of a client,
         // and send cancellations to the daemon.
         private void expungeAllRequests() {
-            int globalId, clientId, i;
+            mClientLogs.log("Client unregistered. expungeAllRequests!");
             // TODO: to keep handler responsive, do not clean all requests for that client at once.
-            for (i = 0; i < mClientIds.size(); i++) {
-                clientId = mClientIds.keyAt(i);
-                globalId = mClientIds.valueAt(i);
+            for (int i = 0; i < mClientRequests.size(); i++) {
+                final int clientId = mClientRequests.keyAt(i);
+                final ClientRequest request = mClientRequests.valueAt(i);
+                final int globalId = request.mGlobalId;
                 mIdToClientInfoMap.remove(globalId);
                 if (DBG) {
                     Log.d(TAG, "Terminating client-ID " + clientId
                             + " global-ID " + globalId + " type " + mClientRequests.get(clientId));
                 }
-                switch (mClientRequests.get(clientId)) {
+
+                if (request instanceof DiscoveryManagerRequest) {
+                    unregisterMdnsListenerFromRequest(request);
+                    continue;
+                }
+
+                if (request instanceof AdvertiserClientRequest) {
+                    mAdvertiser.removeService(globalId);
+                    continue;
+                }
+
+                if (!(request instanceof LegacyClientRequest)) {
+                    throw new IllegalStateException("Unknown request type: " + request.getClass());
+                }
+
+                switch (((LegacyClientRequest) request).mRequestCode) {
                     case NsdManager.DISCOVER_SERVICES:
                         stopServiceDiscovery(globalId);
                         break;
@@ -974,18 +1970,23 @@
                         break;
                 }
             }
-            mClientIds.clear();
             mClientRequests.clear();
         }
 
-        // mClientIds is a sparse array of listener id -> mDnsClient id.  For a given mDnsClient id,
-        // return the corresponding listener id.  mDnsClient id is also called a global id.
+        // mClientRequests is a sparse array of listener id -> ClientRequest.  For a given
+        // mDnsClient id, return the corresponding listener id.  mDnsClient id is also called a
+        // global id.
         private int getClientId(final int globalId) {
-            int idx = mClientIds.indexOfValue(globalId);
-            if (idx < 0) {
-                return idx;
+            for (int i = 0; i < mClientRequests.size(); i++) {
+                if (mClientRequests.valueAt(i).mGlobalId == globalId) {
+                    return mClientRequests.keyAt(i);
+                }
             }
-            return mClientIds.keyAt(idx);
+            return -1;
+        }
+
+        private void log(String message) {
+            mClientLogs.log(message);
         }
 
         void onDiscoverServicesStarted(int listenerKey, NsdServiceInfo info) {
@@ -1083,5 +2084,53 @@
                 Log.e(TAG, "Error calling onResolveServiceSucceeded", e);
             }
         }
+
+        void onStopResolutionFailed(int listenerKey, int error) {
+            try {
+                mCb.onStopResolutionFailed(listenerKey, error);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onStopResolutionFailed", e);
+            }
+        }
+
+        void onStopResolutionSucceeded(int listenerKey) {
+            try {
+                mCb.onStopResolutionSucceeded(listenerKey);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onStopResolutionSucceeded", e);
+            }
+        }
+
+        void onServiceInfoCallbackRegistrationFailed(int listenerKey, int error) {
+            try {
+                mCb.onServiceInfoCallbackRegistrationFailed(listenerKey, error);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onServiceInfoCallbackRegistrationFailed", e);
+            }
+        }
+
+        void onServiceUpdated(int listenerKey, NsdServiceInfo info) {
+            try {
+                mCb.onServiceUpdated(listenerKey, info);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onServiceUpdated", e);
+            }
+        }
+
+        void onServiceUpdatedLost(int listenerKey) {
+            try {
+                mCb.onServiceUpdatedLost(listenerKey);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onServiceUpdatedLost", e);
+            }
+        }
+
+        void onServiceInfoCallbackUnregistered(int listenerKey) {
+            try {
+                mCb.onServiceInfoCallbackUnregistered(listenerKey);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error calling onServiceInfoCallbackUnregistered", e);
+            }
+        }
     }
 }
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/AbstractSocketNetlink.java
similarity index 61%
copy from service/mdns/com/android/server/connectivity/mdns/MdnsAdvertiser.java
copy to service-t/src/com/android/server/connectivity/mdns/AbstractSocketNetlink.java
index dee78fd..b792e46 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/AbstractSocketNetlink.java
@@ -16,14 +16,27 @@
 
 package com.android.server.connectivity.mdns;
 
-import android.util.Log;
-
 /**
- * MdnsAdvertiser manages advertising services per {@link com.android.server.NsdService} requests.
- *
- * TODO: implement
+ * The interface for netlink monitor.
  */
-public class MdnsAdvertiser {
-    private static final String TAG = MdnsAdvertiser.class.getSimpleName();
-    public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+public interface AbstractSocketNetlink {
+
+    /**
+     * Returns if the netlink monitor is supported or not. By default, it is not supported.
+     */
+    default boolean isSupported() {
+        return false;
+    }
+
+    /**
+     * Starts the monitor.
+     */
+    default void startMonitoring() {
+    }
+
+    /**
+     * Stops the monitor.
+     */
+    default void stopMonitoring() {
+    }
 }
diff --git a/service/mdns/com/android/server/connectivity/mdns/ConnectivityMonitor.java b/service-t/src/com/android/server/connectivity/mdns/ConnectivityMonitor.java
similarity index 100%
rename from service/mdns/com/android/server/connectivity/mdns/ConnectivityMonitor.java
rename to service-t/src/com/android/server/connectivity/mdns/ConnectivityMonitor.java
diff --git a/service/mdns/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManager.java b/service-t/src/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManager.java
similarity index 100%
rename from service/mdns/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManager.java
rename to service-t/src/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManager.java
diff --git a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
new file mode 100644
index 0000000..866ecba
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Network;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.server.connectivity.mdns.util.MdnsLogger;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * A {@link Callable} that builds and enqueues a mDNS query to send over the multicast socket. If a
+ * query is built and enqueued successfully, then call to {@link #call()} returns the transaction ID
+ * and the list of the subtypes in the query as a {@link Pair}. If a query is failed to build, or if
+ * it can not be enqueued, then call to {@link #call()} returns {@code null}.
+ */
+public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<String>>> {
+
+    private static final String TAG = "MdnsQueryCallable";
+    private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
+    private static final List<Integer> castShellEmulatorMdnsPorts;
+
+    static {
+        castShellEmulatorMdnsPorts = new ArrayList<>();
+        String[] stringPorts = MdnsConfigs.castShellEmulatorMdnsPorts();
+
+        for (String port : stringPorts) {
+            try {
+                castShellEmulatorMdnsPorts.add(Integer.parseInt(port));
+            } catch (NumberFormatException e) {
+                // Ignore.
+            }
+        }
+    }
+
+    @NonNull
+    private final WeakReference<MdnsSocketClientBase> weakRequestSender;
+    @NonNull
+    private final MdnsPacketWriter packetWriter;
+    @NonNull
+    private final String[] serviceTypeLabels;
+    @NonNull
+    private final List<String> subtypes;
+    private final boolean expectUnicastResponse;
+    private final int transactionId;
+    @Nullable
+    private final Network network;
+    private final boolean sendDiscoveryQueries;
+    @NonNull
+    private final List<MdnsResponse> servicesToResolve;
+
+    EnqueueMdnsQueryCallable(
+            @NonNull MdnsSocketClientBase requestSender,
+            @NonNull MdnsPacketWriter packetWriter,
+            @NonNull String serviceType,
+            @NonNull Collection<String> subtypes,
+            boolean expectUnicastResponse,
+            int transactionId,
+            @Nullable Network network,
+            boolean sendDiscoveryQueries,
+            @NonNull Collection<MdnsResponse> servicesToResolve) {
+        weakRequestSender = new WeakReference<>(requestSender);
+        this.packetWriter = packetWriter;
+        serviceTypeLabels = TextUtils.split(serviceType, "\\.");
+        this.subtypes = new ArrayList<>(subtypes);
+        this.expectUnicastResponse = expectUnicastResponse;
+        this.transactionId = transactionId;
+        this.network = network;
+        this.sendDiscoveryQueries = sendDiscoveryQueries;
+        this.servicesToResolve = new ArrayList<>(servicesToResolve);
+    }
+
+    // Incompatible return type for override of Callable#call().
+    @SuppressWarnings("nullness:override.return.invalid")
+    @Override
+    @Nullable
+    public Pair<Integer, List<String>> call() {
+        try {
+            MdnsSocketClientBase requestSender = weakRequestSender.get();
+            if (requestSender == null) {
+                return null;
+            }
+
+            int numQuestions = 0;
+
+            if (sendDiscoveryQueries) {
+                numQuestions++; // Base service type
+                if (!subtypes.isEmpty()) {
+                    numQuestions += subtypes.size();
+                }
+            }
+
+            // List of (name, type) to query
+            final ArrayList<Pair<String[], Integer>> missingKnownAnswerRecords = new ArrayList<>();
+            for (MdnsResponse response : servicesToResolve) {
+                // TODO: also send queries to renew record TTL (as per RFC6762 7.1 no need to query
+                // if remaining TTL is more than half the original one, so send the queries if half
+                // the TTL has passed).
+                if (response.isComplete()) continue;
+                final String[] serviceName = response.getServiceName();
+                if (serviceName == null) continue;
+                if (!response.hasTextRecord()) {
+                    missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_TXT));
+                }
+                if (!response.hasServiceRecord()) {
+                    missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_SRV));
+                    // The hostname is not yet known, so queries for address records will be sent
+                    // the next time the EnqueueMdnsQueryCallable is enqueued if the reply does not
+                    // contain them. In practice, advertisers should include the address records
+                    // when queried for SRV, although it's not a MUST requirement (RFC6763 12.2).
+                } else if (!response.hasInet4AddressRecord() && !response.hasInet6AddressRecord()) {
+                    final String[] host = response.getServiceRecord().getServiceHost();
+                    missingKnownAnswerRecords.add(new Pair<>(host, MdnsRecord.TYPE_A));
+                    missingKnownAnswerRecords.add(new Pair<>(host, MdnsRecord.TYPE_AAAA));
+                }
+            }
+            numQuestions += missingKnownAnswerRecords.size();
+
+            if (numQuestions == 0) {
+                // No query to send
+                return null;
+            }
+
+            // Header.
+            packetWriter.writeUInt16(transactionId); // transaction ID
+            packetWriter.writeUInt16(MdnsConstants.FLAGS_QUERY); // flags
+            packetWriter.writeUInt16(numQuestions); // number of questions
+            packetWriter.writeUInt16(0); // number of answers (not yet known; will be written later)
+            packetWriter.writeUInt16(0); // number of authority entries
+            packetWriter.writeUInt16(0); // number of additional records
+
+            // Question(s) for missing records on known answers
+            for (Pair<String[], Integer> question : missingKnownAnswerRecords) {
+                writeQuestion(question.first, question.second);
+            }
+
+            // Question(s) for discovering other services with the type. There will be one question
+            // for each (fqdn+subtype, recordType) combination, as well as one for each (fqdn,
+            // recordType) combination.
+            if (sendDiscoveryQueries) {
+                for (String subtype : subtypes) {
+                    String[] labels = new String[serviceTypeLabels.length + 2];
+                    labels[0] = MdnsConstants.SUBTYPE_PREFIX + subtype;
+                    labels[1] = MdnsConstants.SUBTYPE_LABEL;
+                    System.arraycopy(serviceTypeLabels, 0, labels, 2, serviceTypeLabels.length);
+
+                    writeQuestion(labels, MdnsRecord.TYPE_PTR);
+                }
+                writeQuestion(serviceTypeLabels, MdnsRecord.TYPE_PTR);
+            }
+
+            if (requestSender instanceof MdnsMultinetworkSocketClient) {
+                sendPacketToIpv4AndIpv6(requestSender, MdnsConstants.MDNS_PORT, network);
+                for (Integer emulatorPort : castShellEmulatorMdnsPorts) {
+                    sendPacketToIpv4AndIpv6(requestSender, emulatorPort, network);
+                }
+            } else if (requestSender instanceof MdnsSocketClient) {
+                final MdnsSocketClient client = (MdnsSocketClient) requestSender;
+                InetAddress mdnsAddress = MdnsConstants.getMdnsIPv4Address();
+                if (client.isOnIPv6OnlyNetwork()) {
+                    mdnsAddress = MdnsConstants.getMdnsIPv6Address();
+                }
+
+                sendPacketTo(client, new InetSocketAddress(mdnsAddress, MdnsConstants.MDNS_PORT));
+                for (Integer emulatorPort : castShellEmulatorMdnsPorts) {
+                    sendPacketTo(client, new InetSocketAddress(mdnsAddress, emulatorPort));
+                }
+            } else {
+                throw new IOException("Unknown socket client type: " + requestSender.getClass());
+            }
+            return Pair.create(transactionId, subtypes);
+        } catch (IOException e) {
+            LOGGER.e(String.format("Failed to create mDNS packet for subtype: %s.",
+                    TextUtils.join(",", subtypes)), e);
+            return null;
+        }
+    }
+
+    private void writeQuestion(String[] labels, int type) throws IOException {
+        packetWriter.writeLabels(labels);
+        packetWriter.writeUInt16(type);
+        packetWriter.writeUInt16(
+                MdnsConstants.QCLASS_INTERNET
+                        | (expectUnicastResponse ? MdnsConstants.QCLASS_UNICAST : 0));
+    }
+
+    private void sendPacketTo(MdnsSocketClient requestSender, InetSocketAddress address)
+            throws IOException {
+        DatagramPacket packet = packetWriter.getPacket(address);
+        if (expectUnicastResponse) {
+            requestSender.sendUnicastPacket(packet);
+        } else {
+            requestSender.sendMulticastPacket(packet);
+        }
+    }
+
+    private void sendPacketFromNetwork(MdnsSocketClientBase requestSender,
+            InetSocketAddress address, Network network)
+            throws IOException {
+        DatagramPacket packet = packetWriter.getPacket(address);
+        if (expectUnicastResponse) {
+            requestSender.sendUnicastPacket(packet, network);
+        } else {
+            requestSender.sendMulticastPacket(packet, network);
+        }
+    }
+
+    private void sendPacketToIpv4AndIpv6(MdnsSocketClientBase requestSender, int port,
+            Network network) {
+        try {
+            sendPacketFromNetwork(requestSender,
+                    new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), port), network);
+        } catch (IOException e) {
+            Log.i(TAG, "Can't send packet to IPv4", e);
+        }
+        try {
+            sendPacketFromNetwork(requestSender,
+                    new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), port), network);
+        } catch (IOException e) {
+            Log.i(TAG, "Can't send packet to IPv6", e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/service/mdns/com/android/server/connectivity/mdns/ExecutorProvider.java b/service-t/src/com/android/server/connectivity/mdns/ExecutorProvider.java
similarity index 100%
rename from service/mdns/com/android/server/connectivity/mdns/ExecutorProvider.java
rename to service-t/src/com/android/server/connectivity/mdns/ExecutorProvider.java
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
new file mode 100644
index 0000000..5f27b6a
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -0,0 +1,528 @@
+/*
+ * 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 com.android.server.connectivity.mdns;
+
+import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.LinkAddress;
+import android.net.Network;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.os.Looper;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
+
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+
+/**
+ * MdnsAdvertiser manages advertising services per {@link com.android.server.NsdService} requests.
+ *
+ * All methods except the constructor must be called on the looper thread.
+ */
+public class MdnsAdvertiser {
+    private static final String TAG = MdnsAdvertiser.class.getSimpleName();
+    static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+
+    // Top-level domain for link-local queries, as per RFC6762 3.
+    private static final String LOCAL_TLD = "local";
+
+
+    private final Looper mLooper;
+    private final AdvertiserCallback mCb;
+
+    // Max-sized buffers to be used as temporary buffer to read/build packets. May be used by
+    // multiple components, but only for self-contained operations in the looper thread, so not
+    // concurrently.
+    // TODO: set according to MTU. 1300 should fit for ethernet MTU 1500 with some overhead.
+    private final byte[] mPacketCreationBuffer = new byte[1300];
+
+    private final MdnsSocketProvider mSocketProvider;
+    private final ArrayMap<Network, InterfaceAdvertiserRequest> mAdvertiserRequests =
+            new ArrayMap<>();
+    private final ArrayMap<MdnsInterfaceSocket, MdnsInterfaceAdvertiser> mAllAdvertisers =
+            new ArrayMap<>();
+    private final SparseArray<Registration> mRegistrations = new SparseArray<>();
+    private final Dependencies mDeps;
+
+    private String[] mDeviceHostName;
+    @NonNull private final SharedLog mSharedLog;
+
+    /**
+     * Dependencies for {@link MdnsAdvertiser}, useful for testing.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        /**
+         * @see MdnsInterfaceAdvertiser
+         */
+        public MdnsInterfaceAdvertiser makeAdvertiser(@NonNull MdnsInterfaceSocket socket,
+                @NonNull List<LinkAddress> initialAddresses,
+                @NonNull Looper looper, @NonNull byte[] packetCreationBuffer,
+                @NonNull MdnsInterfaceAdvertiser.Callback cb,
+                @NonNull String[] deviceHostName,
+                @NonNull SharedLog sharedLog) {
+            // Note NetworkInterface is final and not mockable
+            return new MdnsInterfaceAdvertiser(socket, initialAddresses, looper,
+                    packetCreationBuffer, cb, deviceHostName, sharedLog);
+        }
+
+        /**
+         * Generates a unique hostname to be used by the device.
+         */
+        @NonNull
+        public String[] generateHostname() {
+            // Generate a very-probably-unique hostname. This allows minimizing possible conflicts
+            // to the point that probing for it is no longer necessary (as per RFC6762 8.1 last
+            // paragraph), and does not leak more information than what could already be obtained by
+            // looking at the mDNS packets source address.
+            // This differs from historical behavior that just used "Android.local" for many
+            // devices, creating a lot of conflicts.
+            // Having a different hostname per interface is an acceptable option as per RFC6762 14.
+            // This hostname will change every time the interface is reconnected, so this does not
+            // allow tracking the device.
+            // TODO: consider deriving a hostname from other sources, such as the IPv6 addresses
+            // (reusing the same privacy-protecting mechanics).
+            return new String[] {
+                    "Android_" + UUID.randomUUID().toString().replace("-", ""), LOCAL_TLD };
+        }
+    }
+
+    private final MdnsInterfaceAdvertiser.Callback mInterfaceAdvertiserCb =
+            new MdnsInterfaceAdvertiser.Callback() {
+        @Override
+        public void onRegisterServiceSucceeded(
+                @NonNull MdnsInterfaceAdvertiser advertiser, int serviceId) {
+            // Wait for all current interfaces to be done probing before notifying of success.
+            if (any(mAllAdvertisers, (k, a) -> a.isProbing(serviceId))) return;
+            // The service may still be unregistered/renamed if a conflict is found on a later added
+            // interface, or if a conflicting announcement/reply is detected (RFC6762 9.)
+
+            final Registration registration = mRegistrations.get(serviceId);
+            if (registration == null) {
+                Log.wtf(TAG, "Register succeeded for unknown registration");
+                return;
+            }
+            if (!registration.mNotifiedRegistrationSuccess) {
+                mCb.onRegisterServiceSucceeded(serviceId, registration.getServiceInfo());
+                registration.mNotifiedRegistrationSuccess = true;
+            }
+        }
+
+        @Override
+        public void onServiceConflict(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId) {
+            mSharedLog.i("Found conflict, restarted probing for service " + serviceId);
+
+            final Registration registration = mRegistrations.get(serviceId);
+            if (registration == null) return;
+            if (registration.mNotifiedRegistrationSuccess) {
+                // TODO: consider notifying clients that the service is no longer registered with
+                // the old name (back to probing). The legacy implementation did not send any
+                // callback though; it only sent onServiceRegistered after re-probing finishes
+                // (with the old, conflicting, actually not used name as argument... The new
+                // implementation will send callbacks with the new name).
+                registration.mNotifiedRegistrationSuccess = false;
+
+                // The service was done probing, just reset it to probing state (RFC6762 9.)
+                forAllAdvertisers(a -> a.restartProbingForConflict(serviceId));
+                return;
+            }
+
+            // Conflict was found during probing; rename once to find a name that has no conflict
+            registration.updateForConflict(
+                    registration.makeNewServiceInfoForConflict(1 /* renameCount */),
+                    1 /* renameCount */);
+
+            // Keep renaming if the new name conflicts in local registrations
+            updateRegistrationUntilNoConflict((net, adv) -> adv.hasRegistration(registration),
+                    registration);
+
+            // Update advertisers to use the new name
+            forAllAdvertisers(a -> a.renameServiceForConflict(
+                    serviceId, registration.getServiceInfo()));
+        }
+
+        @Override
+        public void onDestroyed(@NonNull MdnsInterfaceSocket socket) {
+            for (int i = mAdvertiserRequests.size() - 1; i >= 0; i--) {
+                if (mAdvertiserRequests.valueAt(i).onAdvertiserDestroyed(socket)) {
+                    mAdvertiserRequests.removeAt(i);
+                }
+            }
+            mAllAdvertisers.remove(socket);
+        }
+    };
+
+    private boolean hasAnyConflict(
+            @NonNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter,
+            @NonNull NsdServiceInfo newInfo) {
+        return any(mAdvertiserRequests, (network, adv) ->
+                applicableAdvertiserFilter.test(network, adv) && adv.hasConflict(newInfo));
+    }
+
+    private void updateRegistrationUntilNoConflict(
+            @NonNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter,
+            @NonNull Registration registration) {
+        int renameCount = 0;
+        NsdServiceInfo newInfo = registration.getServiceInfo();
+        while (hasAnyConflict(applicableAdvertiserFilter, newInfo)) {
+            renameCount++;
+            newInfo = registration.makeNewServiceInfoForConflict(renameCount);
+        }
+        registration.updateForConflict(newInfo, renameCount);
+    }
+
+    /**
+     * A request for a {@link MdnsInterfaceAdvertiser}.
+     *
+     * This class tracks services to be advertised on all sockets provided via a registered
+     * {@link MdnsSocketProvider.SocketCallback}.
+     */
+    private class InterfaceAdvertiserRequest implements MdnsSocketProvider.SocketCallback {
+        /** Registrations to add to newer MdnsInterfaceAdvertisers when sockets are created. */
+        @NonNull
+        private final SparseArray<Registration> mPendingRegistrations = new SparseArray<>();
+        @NonNull
+        private final ArrayMap<MdnsInterfaceSocket, MdnsInterfaceAdvertiser> mAdvertisers =
+                new ArrayMap<>();
+
+        InterfaceAdvertiserRequest(@Nullable Network requestedNetwork) {
+            mSocketProvider.requestSocket(requestedNetwork, this);
+        }
+
+        /**
+         * Called when an advertiser was destroyed, after all services were unregistered and it sent
+         * exit announcements, or the interface is gone.
+         *
+         * @return true if this {@link InterfaceAdvertiserRequest} should now be deleted.
+         */
+        boolean onAdvertiserDestroyed(@NonNull MdnsInterfaceSocket socket) {
+            mAdvertisers.remove(socket);
+            if (mAdvertisers.size() == 0 && mPendingRegistrations.size() == 0) {
+                // No advertiser is using sockets from this request anymore (in particular for exit
+                // announcements), and there is no registration so newer sockets will not be
+                // necessary, so the request can be unregistered.
+                mSocketProvider.unrequestSocket(this);
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * Return whether this {@link InterfaceAdvertiserRequest} has the given registration.
+         */
+        boolean hasRegistration(@NonNull Registration registration) {
+            return mPendingRegistrations.indexOfValue(registration) >= 0;
+        }
+
+        /**
+         * Return whether using the proposed new {@link NsdServiceInfo} to add a registration would
+         * cause a conflict in this {@link InterfaceAdvertiserRequest}.
+         */
+        boolean hasConflict(@NonNull NsdServiceInfo newInfo) {
+            return getConflictingService(newInfo) >= 0;
+        }
+
+        /**
+         * Get the ID of a conflicting service, or -1 if none.
+         */
+        int getConflictingService(@NonNull NsdServiceInfo info) {
+            for (int i = 0; i < mPendingRegistrations.size(); i++) {
+                final NsdServiceInfo other = mPendingRegistrations.valueAt(i).getServiceInfo();
+                if (MdnsUtils.equalsIgnoreDnsCase(info.getServiceName(), other.getServiceName())
+                        && MdnsUtils.equalsIgnoreDnsCase(info.getServiceType(),
+                        other.getServiceType())) {
+                    return mPendingRegistrations.keyAt(i);
+                }
+            }
+            return -1;
+        }
+
+        /**
+         * Add a service.
+         *
+         * Conflicts must be checked via {@link #getConflictingService} before attempting to add.
+         */
+        void addService(int id, Registration registration) {
+            mPendingRegistrations.put(id, registration);
+            for (int i = 0; i < mAdvertisers.size(); i++) {
+                try {
+                    mAdvertisers.valueAt(i).addService(
+                            id, registration.getServiceInfo(), registration.getSubtype());
+                } catch (NameConflictException e) {
+                    Log.wtf(TAG, "Name conflict adding services that should have unique names", e);
+                }
+            }
+        }
+
+        void removeService(int id) {
+            mPendingRegistrations.remove(id);
+            for (int i = 0; i < mAdvertisers.size(); i++) {
+                mAdvertisers.valueAt(i).removeService(id);
+            }
+        }
+
+        @Override
+        public void onSocketCreated(@NonNull Network network,
+                @NonNull MdnsInterfaceSocket socket,
+                @NonNull List<LinkAddress> addresses) {
+            MdnsInterfaceAdvertiser advertiser = mAllAdvertisers.get(socket);
+            if (advertiser == null) {
+                advertiser = mDeps.makeAdvertiser(socket, addresses, mLooper, mPacketCreationBuffer,
+                        mInterfaceAdvertiserCb, mDeviceHostName,
+                        mSharedLog.forSubComponent(socket.getInterface().getName()));
+                mAllAdvertisers.put(socket, advertiser);
+                advertiser.start();
+            }
+            mAdvertisers.put(socket, advertiser);
+            for (int i = 0; i < mPendingRegistrations.size(); i++) {
+                final Registration registration = mPendingRegistrations.valueAt(i);
+                try {
+                    advertiser.addService(mPendingRegistrations.keyAt(i),
+                            registration.getServiceInfo(), registration.getSubtype());
+                } catch (NameConflictException e) {
+                    Log.wtf(TAG, "Name conflict adding services that should have unique names", e);
+                }
+            }
+        }
+
+        @Override
+        public void onInterfaceDestroyed(@NonNull Network network,
+                @NonNull MdnsInterfaceSocket socket) {
+            final MdnsInterfaceAdvertiser advertiser = mAdvertisers.get(socket);
+            if (advertiser != null) advertiser.destroyNow();
+        }
+
+        @Override
+        public void onAddressesChanged(@NonNull Network network,
+                @NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> addresses) {
+            final MdnsInterfaceAdvertiser advertiser = mAdvertisers.get(socket);
+            if (advertiser != null) advertiser.updateAddresses(addresses);
+        }
+    }
+
+    private static class Registration {
+        @NonNull
+        final String mOriginalName;
+        boolean mNotifiedRegistrationSuccess;
+        private int mConflictCount;
+        @NonNull
+        private NsdServiceInfo mServiceInfo;
+        @Nullable
+        private final String mSubtype;
+
+        private Registration(@NonNull NsdServiceInfo serviceInfo, @Nullable String subtype) {
+            this.mOriginalName = serviceInfo.getServiceName();
+            this.mServiceInfo = serviceInfo;
+            this.mSubtype = subtype;
+        }
+
+        /**
+         * Update the registration to use a different service name, after a conflict was found.
+         *
+         * @param newInfo New service info to use.
+         * @param renameCount How many renames were done before reaching the current name.
+         */
+        private void updateForConflict(@NonNull NsdServiceInfo newInfo, int renameCount) {
+            mConflictCount += renameCount;
+            mServiceInfo = newInfo;
+        }
+
+        /**
+         * Make a new service name for the registration, after a conflict was found.
+         *
+         * If a name conflict was found during probing or because different advertising requests
+         * used the same name, the registration is attempted again with a new name (here using
+         * a number suffix, (1), (2) etc). Registration success is notified once probing succeeds
+         * with a new name. This matches legacy behavior based on mdnsresponder, and appendix D of
+         * RFC6763.
+         *
+         * @param renameCount How much to increase the number suffix for this conflict.
+         */
+        @NonNull
+        public NsdServiceInfo makeNewServiceInfoForConflict(int renameCount) {
+            // In case of conflict choose a different service name. After the first conflict use
+            // "Name (2)", then "Name (3)" etc.
+            // TODO: use a hidden method in NsdServiceInfo once MdnsAdvertiser is moved to service-t
+            final NsdServiceInfo newInfo = new NsdServiceInfo();
+            newInfo.setServiceName(getUpdatedServiceName(renameCount));
+            newInfo.setServiceType(mServiceInfo.getServiceType());
+            for (Map.Entry<String, byte[]> attr : mServiceInfo.getAttributes().entrySet()) {
+                newInfo.setAttribute(attr.getKey(),
+                        attr.getValue() == null ? null : new String(attr.getValue()));
+            }
+            newInfo.setHost(mServiceInfo.getHost());
+            newInfo.setPort(mServiceInfo.getPort());
+            newInfo.setNetwork(mServiceInfo.getNetwork());
+            // interfaceIndex is not set when registering
+            return newInfo;
+        }
+
+        private String getUpdatedServiceName(int renameCount) {
+            final String suffix = " (" + (mConflictCount + renameCount + 1) + ")";
+            final String truncatedServiceName = MdnsUtils.truncateServiceName(mOriginalName,
+                    MAX_LABEL_LENGTH - suffix.length());
+            return truncatedServiceName + suffix;
+        }
+
+        @NonNull
+        public NsdServiceInfo getServiceInfo() {
+            return mServiceInfo;
+        }
+
+        @Nullable
+        public String getSubtype() {
+            return mSubtype;
+        }
+    }
+
+    /**
+     * Callbacks for advertising services.
+     *
+     * Every method is called on the MdnsAdvertiser looper thread.
+     */
+    public interface AdvertiserCallback {
+        /**
+         * Called when a service was successfully registered, after probing.
+         *
+         * @param serviceId ID of the service provided when registering.
+         * @param registeredInfo Registered info, which may be different from the requested info,
+         *                       after probing and possibly choosing alternative service names.
+         */
+        void onRegisterServiceSucceeded(int serviceId, NsdServiceInfo registeredInfo);
+
+        /**
+         * Called when service registration failed.
+         *
+         * @param serviceId ID of the service provided when registering.
+         * @param errorCode One of {@code NsdManager.FAILURE_*}
+         */
+        void onRegisterServiceFailed(int serviceId, int errorCode);
+
+        // Unregistration is notified immediately as success in NsdService so no callback is needed
+        // here.
+    }
+
+    public MdnsAdvertiser(@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
+            @NonNull AdvertiserCallback cb, @NonNull SharedLog sharedLog) {
+        this(looper, socketProvider, cb, new Dependencies(), sharedLog);
+    }
+
+    @VisibleForTesting
+    MdnsAdvertiser(@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
+            @NonNull AdvertiserCallback cb, @NonNull Dependencies deps,
+            @NonNull SharedLog sharedLog) {
+        mLooper = looper;
+        mCb = cb;
+        mSocketProvider = socketProvider;
+        mDeps = deps;
+        mDeviceHostName = deps.generateHostname();
+        mSharedLog = sharedLog;
+    }
+
+    private void checkThread() {
+        if (Thread.currentThread() != mLooper.getThread()) {
+            throw new IllegalStateException("This must be called on the looper thread");
+        }
+    }
+
+    /**
+     * Add a service to advertise.
+     * @param id A unique ID for the service.
+     * @param service The service info to advertise.
+     * @param subtype An optional subtype to advertise the service with.
+     */
+    public void addService(int id, NsdServiceInfo service, @Nullable String subtype) {
+        checkThread();
+        if (mRegistrations.get(id) != null) {
+            Log.e(TAG, "Adding duplicate registration for " + service);
+            // TODO (b/264986328): add a more specific error code
+            mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR);
+            return;
+        }
+
+        mSharedLog.i("Adding service " + service + " with ID " + id + " and subtype " + subtype);
+
+        final Network network = service.getNetwork();
+        final Registration registration = new Registration(service, subtype);
+        final BiPredicate<Network, InterfaceAdvertiserRequest> checkConflictFilter;
+        if (network == null) {
+            // If registering on all networks, no advertiser must have conflicts
+            checkConflictFilter = (net, adv) -> true;
+        } else {
+            // If registering on one network, the matching network advertiser and the one for all
+            // networks must not have conflicts
+            checkConflictFilter = (net, adv) -> net == null || network.equals(net);
+        }
+
+        updateRegistrationUntilNoConflict(checkConflictFilter, registration);
+
+        InterfaceAdvertiserRequest advertiser = mAdvertiserRequests.get(network);
+        if (advertiser == null) {
+            advertiser = new InterfaceAdvertiserRequest(network);
+            mAdvertiserRequests.put(network, advertiser);
+        }
+        advertiser.addService(id, registration);
+        mRegistrations.put(id, registration);
+    }
+
+    /**
+     * Remove a previously added service.
+     * @param id ID used when registering.
+     */
+    public void removeService(int id) {
+        checkThread();
+        if (!mRegistrations.contains(id)) return;
+        mSharedLog.i("Removing service with ID " + id);
+        for (int i = mAdvertiserRequests.size() - 1; i >= 0; i--) {
+            final InterfaceAdvertiserRequest advertiser = mAdvertiserRequests.valueAt(i);
+            advertiser.removeService(id);
+        }
+        mRegistrations.remove(id);
+        // Regenerates host name when registrations removed.
+        if (mRegistrations.size() == 0) {
+            mDeviceHostName = mDeps.generateHostname();
+        }
+    }
+
+    private static <K, V> boolean any(@NonNull ArrayMap<K, V> map,
+            @NonNull BiPredicate<K, V> predicate) {
+        for (int i = 0; i < map.size(); i++) {
+            if (predicate.test(map.keyAt(i), map.valueAt(i))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void forAllAdvertisers(@NonNull Consumer<MdnsInterfaceAdvertiser> consumer) {
+        any(mAllAdvertisers, (socket, advertiser) -> {
+            consumer.accept(advertiser);
+            return false;
+        });
+    }
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java
new file mode 100644
index 0000000..27fc945
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAnnouncer.java
@@ -0,0 +1,122 @@
+/*
+ * 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 com.android.server.connectivity.mdns;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Looper;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Sends mDns announcements when a service registration changes and at regular intervals.
+ *
+ * This allows maintaining other hosts' caches up-to-date. See RFC6762 8.3.
+ */
+public class MdnsAnnouncer extends MdnsPacketRepeater<MdnsAnnouncer.BaseAnnouncementInfo> {
+    private static final long ANNOUNCEMENT_INITIAL_DELAY_MS = 1000L;
+    @VisibleForTesting
+    static final int ANNOUNCEMENT_COUNT = 8;
+
+    // Matches delay and GoodbyeCount used by the legacy implementation
+    private static final long EXIT_DELAY_MS = 2000L;
+    private static final int EXIT_COUNT = 3;
+
+    @NonNull
+    private final String mLogTag;
+
+    /** Base class for announcement requests to send with {@link MdnsAnnouncer}. */
+    public abstract static class BaseAnnouncementInfo implements MdnsPacketRepeater.Request {
+        private final int mServiceId;
+        @NonNull
+        private final MdnsPacket mPacket;
+
+        protected BaseAnnouncementInfo(int serviceId, @NonNull List<MdnsRecord> announcedRecords,
+                @NonNull List<MdnsRecord> additionalRecords) {
+            mServiceId = serviceId;
+            final int flags = 0x8400; // Response, authoritative (rfc6762 18.4)
+            mPacket = new MdnsPacket(flags,
+                    Collections.emptyList() /* questions */,
+                    announcedRecords,
+                    Collections.emptyList() /* authorityRecords */,
+                    additionalRecords);
+        }
+
+        public int getServiceId() {
+            return mServiceId;
+        }
+
+        @Override
+        public MdnsPacket getPacket(int index) {
+            return mPacket;
+        }
+    }
+
+    /** Announcement request to send with {@link MdnsAnnouncer}. */
+    public static class AnnouncementInfo extends BaseAnnouncementInfo {
+        AnnouncementInfo(int serviceId, List<MdnsRecord> announcedRecords,
+                List<MdnsRecord> additionalRecords) {
+            super(serviceId, announcedRecords, additionalRecords);
+        }
+
+        @Override
+        public long getDelayMs(int nextIndex) {
+            // Delay is doubled for each announcement
+            return ANNOUNCEMENT_INITIAL_DELAY_MS << (nextIndex - 1);
+        }
+
+        @Override
+        public int getNumSends() {
+            return ANNOUNCEMENT_COUNT;
+        }
+    }
+
+    /** Service exit announcement request to send with {@link MdnsAnnouncer}. */
+    public static class ExitAnnouncementInfo extends BaseAnnouncementInfo {
+        ExitAnnouncementInfo(int serviceId, List<MdnsRecord> announcedRecords) {
+            super(serviceId, announcedRecords, Collections.emptyList() /* additionalRecords */);
+        }
+
+        @Override
+        public long getDelayMs(int nextIndex) {
+            return EXIT_DELAY_MS;
+        }
+
+        @Override
+        public int getNumSends() {
+            return EXIT_COUNT;
+        }
+    }
+
+    public MdnsAnnouncer(@NonNull String interfaceTag, @NonNull Looper looper,
+            @NonNull MdnsReplySender replySender,
+            @Nullable PacketRepeaterCallback<BaseAnnouncementInfo> cb) {
+        super(looper, replySender, cb);
+        mLogTag = MdnsAnnouncer.class.getSimpleName() + "/" + interfaceTag;
+    }
+
+    @Override
+    protected String getTag() {
+        return mLogTag;
+    }
+
+    // TODO: Notify MdnsRecordRepository that the records were announced for that service ID,
+    // so it can update the last advertised timestamp of the associated records.
+}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsAnyRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAnyRecord.java
similarity index 100%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsAnyRecord.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsAnyRecord.java
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsConfigs.java b/service-t/src/com/android/server/connectivity/mdns/MdnsConfigs.java
similarity index 100%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsConfigs.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsConfigs.java
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsConstants.java b/service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java
similarity index 97%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsConstants.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java
index 396be5f..f0e1717 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsConstants.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsConstants.java
@@ -37,6 +37,7 @@
     public static final int FLAGS_QUERY = 0x0000;
     public static final int FLAGS_RESPONSE_MASK = 0xF80F;
     public static final int FLAGS_RESPONSE = 0x8000;
+    public static final int FLAG_TRUNCATED = 0x0200;
     public static final int QCLASS_INTERNET = 0x0001;
     public static final int QCLASS_UNICAST = 0x8000;
     public static final String SUBTYPE_LABEL = "_sub";
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
new file mode 100644
index 0000000..0f30f10
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns;
+
+import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
+
+import android.Manifest.permission;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.net.Network;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * This class keeps tracking the set of registered {@link MdnsServiceBrowserListener} instances, and
+ * notify them when a mDNS service instance is found, updated, or removed?
+ */
+public class MdnsDiscoveryManager implements MdnsSocketClientBase.Callback {
+    private static final String TAG = MdnsDiscoveryManager.class.getSimpleName();
+    public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final ExecutorProvider executorProvider;
+    private final MdnsSocketClientBase socketClient;
+    @NonNull private final SharedLog sharedLog;
+
+    @NonNull private final PerNetworkServiceTypeClients perNetworkServiceTypeClients;
+    @NonNull private final Handler handler;
+    @Nullable private final HandlerThread handlerThread;
+
+    private static class PerNetworkServiceTypeClients {
+        private final ArrayMap<Pair<String, Network>, MdnsServiceTypeClient> clients =
+                new ArrayMap<>();
+
+        public void put(@NonNull String serviceType, @Nullable Network network,
+                @NonNull MdnsServiceTypeClient client) {
+            final String dnsLowerServiceType = MdnsUtils.toDnsLowerCase(serviceType);
+            final Pair<String, Network> perNetworkServiceType = new Pair<>(dnsLowerServiceType,
+                    network);
+            clients.put(perNetworkServiceType, client);
+        }
+
+        @Nullable
+        public MdnsServiceTypeClient get(@NonNull String serviceType, @Nullable Network network) {
+            final String dnsLowerServiceType = MdnsUtils.toDnsLowerCase(serviceType);
+            final Pair<String, Network> perNetworkServiceType = new Pair<>(dnsLowerServiceType,
+                    network);
+            return clients.getOrDefault(perNetworkServiceType, null);
+        }
+
+        public List<MdnsServiceTypeClient> getByServiceType(@NonNull String serviceType) {
+            final String dnsLowerServiceType = MdnsUtils.toDnsLowerCase(serviceType);
+            final List<MdnsServiceTypeClient> list = new ArrayList<>();
+            for (int i = 0; i < clients.size(); i++) {
+                final Pair<String, Network> perNetworkServiceType = clients.keyAt(i);
+                if (dnsLowerServiceType.equals(perNetworkServiceType.first)) {
+                    list.add(clients.valueAt(i));
+                }
+            }
+            return list;
+        }
+
+        public List<MdnsServiceTypeClient> getByNetwork(@Nullable Network network) {
+            final List<MdnsServiceTypeClient> list = new ArrayList<>();
+            for (int i = 0; i < clients.size(); i++) {
+                final Pair<String, Network> perNetworkServiceType = clients.keyAt(i);
+                final Network serviceTypeNetwork = perNetworkServiceType.second;
+                if (Objects.equals(network, serviceTypeNetwork)) {
+                    list.add(clients.valueAt(i));
+                }
+            }
+            return list;
+        }
+
+        public void remove(@NonNull MdnsServiceTypeClient client) {
+            final int index = clients.indexOfValue(client);
+            clients.removeAt(index);
+        }
+
+        public boolean isEmpty() {
+            return clients.isEmpty();
+        }
+    }
+
+    public MdnsDiscoveryManager(@NonNull ExecutorProvider executorProvider,
+            @NonNull MdnsSocketClientBase socketClient, @NonNull SharedLog sharedLog) {
+        this.executorProvider = executorProvider;
+        this.socketClient = socketClient;
+        this.sharedLog = sharedLog;
+        this.perNetworkServiceTypeClients = new PerNetworkServiceTypeClients();
+        if (socketClient.getLooper() != null) {
+            this.handlerThread = null;
+            this.handler = new Handler(socketClient.getLooper());
+        } else {
+            this.handlerThread = new HandlerThread(MdnsDiscoveryManager.class.getSimpleName());
+            this.handlerThread.start();
+            this.handler = new Handler(handlerThread.getLooper());
+        }
+    }
+
+    private void checkAndRunOnHandlerThread(@NonNull Runnable function) {
+        if (this.handlerThread == null) {
+            function.run();
+        } else {
+            handler.post(function);
+        }
+    }
+
+    /**
+     * Do the cleanup of the MdnsDiscoveryManager
+     */
+    public void shutDown() {
+        if (this.handlerThread != null) {
+            this.handlerThread.quitSafely();
+        }
+    }
+
+    /**
+     * Starts (or continue) to discovery mDNS services with given {@code serviceType}, and registers
+     * {@code listener} for receiving mDNS service discovery responses.
+     *
+     * @param serviceType   The type of the service to discover.
+     * @param listener      The {@link MdnsServiceBrowserListener} listener.
+     * @param searchOptions The {@link MdnsSearchOptions} to be used for discovering {@code
+     *                      serviceType}.
+     */
+    @RequiresPermission(permission.CHANGE_WIFI_MULTICAST_STATE)
+    public void registerListener(
+            @NonNull String serviceType,
+            @NonNull MdnsServiceBrowserListener listener,
+            @NonNull MdnsSearchOptions searchOptions) {
+        sharedLog.i("Registering listener for serviceType: " + serviceType);
+        checkAndRunOnHandlerThread(() ->
+                handleRegisterListener(serviceType, listener, searchOptions));
+    }
+
+    private void handleRegisterListener(
+            @NonNull String serviceType,
+            @NonNull MdnsServiceBrowserListener listener,
+            @NonNull MdnsSearchOptions searchOptions) {
+        if (perNetworkServiceTypeClients.isEmpty()) {
+            // First listener. Starts the socket client.
+            try {
+                socketClient.startDiscovery();
+            } catch (IOException e) {
+                sharedLog.e("Failed to start discover.", e);
+                return;
+            }
+        }
+        // Request the network for discovery.
+        socketClient.notifyNetworkRequested(listener, searchOptions.getNetwork(),
+                new MdnsSocketClientBase.SocketCreationCallback() {
+                    @Override
+                    public void onSocketCreated(@Nullable Network network) {
+                        ensureRunningOnHandlerThread(handler);
+                        // All listeners of the same service types shares the same
+                        // MdnsServiceTypeClient.
+                        MdnsServiceTypeClient serviceTypeClient =
+                                perNetworkServiceTypeClients.get(serviceType, network);
+                        if (serviceTypeClient == null) {
+                            serviceTypeClient = createServiceTypeClient(serviceType, network);
+                            perNetworkServiceTypeClients.put(serviceType, network,
+                                    serviceTypeClient);
+                        }
+                        serviceTypeClient.startSendAndReceive(listener, searchOptions);
+                    }
+
+                    @Override
+                    public void onAllSocketsDestroyed(@Nullable Network network) {
+                        ensureRunningOnHandlerThread(handler);
+                        final MdnsServiceTypeClient serviceTypeClient =
+                                perNetworkServiceTypeClients.get(serviceType, network);
+                        if (serviceTypeClient == null) return;
+                        // Notify all listeners that all services are removed from this socket.
+                        serviceTypeClient.notifySocketDestroyed();
+                        perNetworkServiceTypeClients.remove(serviceTypeClient);
+                    }
+                });
+    }
+
+    /**
+     * Unregister {@code listener} for receiving mDNS service discovery responses. IF no listener is
+     * registered for the given service type, stops discovery for the service type.
+     *
+     * @param serviceType The type of the service to discover.
+     * @param listener    The {@link MdnsServiceBrowserListener} listener.
+     */
+    @RequiresPermission(permission.CHANGE_WIFI_MULTICAST_STATE)
+    public void unregisterListener(
+            @NonNull String serviceType, @NonNull MdnsServiceBrowserListener listener) {
+        sharedLog.i("Unregistering listener for serviceType:" + serviceType);
+        checkAndRunOnHandlerThread(() -> handleUnregisterListener(serviceType, listener));
+    }
+
+    private void handleUnregisterListener(
+            @NonNull String serviceType, @NonNull MdnsServiceBrowserListener listener) {
+        final List<MdnsServiceTypeClient> serviceTypeClients =
+                perNetworkServiceTypeClients.getByServiceType(serviceType);
+        if (serviceTypeClients.isEmpty()) {
+            return;
+        }
+        for (int i = 0; i < serviceTypeClients.size(); i++) {
+            final MdnsServiceTypeClient serviceTypeClient = serviceTypeClients.get(i);
+            if (serviceTypeClient.stopSendAndReceive(listener)) {
+                // No listener is registered for the service type anymore, remove it from the list
+                // of the service type clients.
+                perNetworkServiceTypeClients.remove(serviceTypeClient);
+            }
+        }
+        if (perNetworkServiceTypeClients.isEmpty()) {
+            // No discovery request. Stops the socket client.
+            socketClient.stopDiscovery();
+        }
+        // Unrequested the network.
+        socketClient.notifyNetworkUnrequested(listener);
+    }
+
+    @Override
+    public void onResponseReceived(@NonNull MdnsPacket packet,
+            int interfaceIndex, @Nullable Network network) {
+        checkAndRunOnHandlerThread(() ->
+                handleOnResponseReceived(packet, interfaceIndex, network));
+    }
+
+    private void handleOnResponseReceived(@NonNull MdnsPacket packet, int interfaceIndex,
+            @Nullable Network network) {
+        for (MdnsServiceTypeClient serviceTypeClient
+                : perNetworkServiceTypeClients.getByNetwork(network)) {
+            serviceTypeClient.processResponse(packet, interfaceIndex, network);
+        }
+    }
+
+    @Override
+    public void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode,
+            @Nullable Network network) {
+        checkAndRunOnHandlerThread(() ->
+                handleOnFailedToParseMdnsResponse(receivedPacketNumber, errorCode, network));
+    }
+
+    private void handleOnFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode,
+            @Nullable Network network) {
+        for (MdnsServiceTypeClient serviceTypeClient
+                : perNetworkServiceTypeClients.getByNetwork(network)) {
+            serviceTypeClient.onFailedToParseMdnsResponse(receivedPacketNumber, errorCode);
+        }
+    }
+
+    @VisibleForTesting
+    MdnsServiceTypeClient createServiceTypeClient(@NonNull String serviceType,
+            @Nullable Network network) {
+        sharedLog.log("createServiceTypeClient for type:" + serviceType + ", net:" + network);
+        return new MdnsServiceTypeClient(
+                serviceType, socketClient,
+                executorProvider.newServiceTypeClientSchedulerExecutor(), network,
+                sharedLog.forSubComponent(serviceType + "-" + network));
+    }
+}
\ No newline at end of file
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java
similarity index 100%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
new file mode 100644
index 0000000..724a704
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -0,0 +1,349 @@
+/*
+ * 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 com.android.server.connectivity.mdns;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.LinkAddress;
+import android.net.nsd.NsdServiceInfo;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.HexDump;
+import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.mdns.MdnsAnnouncer.BaseAnnouncementInfo;
+import com.android.server.connectivity.mdns.MdnsPacketRepeater.PacketRepeaterCallback;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.List;
+
+/**
+ * A class that handles advertising services on a {@link MdnsInterfaceSocket} tied to an interface.
+ */
+public class MdnsInterfaceAdvertiser implements MulticastPacketReader.PacketHandler {
+    private static final boolean DBG = MdnsAdvertiser.DBG;
+    @VisibleForTesting
+    public static final long EXIT_ANNOUNCEMENT_DELAY_MS = 100L;
+    @NonNull
+    private final String mTag;
+    @NonNull
+    private final ProbingCallback mProbingCallback = new ProbingCallback();
+    @NonNull
+    private final AnnouncingCallback mAnnouncingCallback = new AnnouncingCallback();
+    @NonNull
+    private final MdnsRecordRepository mRecordRepository;
+    @NonNull
+    private final Callback mCb;
+    // Callbacks are on the same looper thread, but posted to the next handler loop
+    @NonNull
+    private final Handler mCbHandler;
+    @NonNull
+    private final MdnsInterfaceSocket mSocket;
+    @NonNull
+    private final MdnsAnnouncer mAnnouncer;
+    @NonNull
+    private final MdnsProber mProber;
+    @NonNull
+    private final MdnsReplySender mReplySender;
+
+    @NonNull
+    private final SharedLog mSharedLog;
+
+    /**
+     * Callbacks called by {@link MdnsInterfaceAdvertiser} to report status updates.
+     */
+    interface Callback {
+        /**
+         * Called by the advertiser after it successfully registered a service, after probing.
+         */
+        void onRegisterServiceSucceeded(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId);
+
+        /**
+         * Called by the advertiser when a conflict was found, during or after probing.
+         *
+         * If a conflict is found during probing, the {@link #renameServiceForConflict} must be
+         * called to restart probing and attempt registration with a different name.
+         */
+        void onServiceConflict(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId);
+
+        /**
+         * Called by the advertiser when it destroyed itself.
+         *
+         * This can happen after a call to {@link #destroyNow()}, or after all services were
+         * unregistered and the advertiser finished sending exit announcements.
+         */
+        void onDestroyed(@NonNull MdnsInterfaceSocket socket);
+    }
+
+    /**
+     * Callbacks from {@link MdnsProber}.
+     */
+    private class ProbingCallback implements
+            PacketRepeaterCallback<MdnsProber.ProbingInfo> {
+        @Override
+        public void onFinished(MdnsProber.ProbingInfo info) {
+            final MdnsAnnouncer.AnnouncementInfo announcementInfo;
+            mSharedLog.i("Probing finished for service " + info.getServiceId());
+            mCbHandler.post(() -> mCb.onRegisterServiceSucceeded(
+                    MdnsInterfaceAdvertiser.this, info.getServiceId()));
+            try {
+                announcementInfo = mRecordRepository.onProbingSucceeded(info);
+            } catch (IOException e) {
+                mSharedLog.e("Error building announcements", e);
+                return;
+            }
+
+            mAnnouncer.startSending(info.getServiceId(), announcementInfo,
+                    0L /* initialDelayMs */);
+        }
+    }
+
+    /**
+     * Callbacks from {@link MdnsAnnouncer}.
+     */
+    private class AnnouncingCallback implements PacketRepeaterCallback<BaseAnnouncementInfo> {
+        @Override
+        public void onSent(int index, @NonNull BaseAnnouncementInfo info) {
+            mRecordRepository.onAdvertisementSent(info.getServiceId());
+        }
+
+        @Override
+        public void onFinished(@NonNull BaseAnnouncementInfo info) {
+            if (info instanceof MdnsAnnouncer.ExitAnnouncementInfo) {
+                mRecordRepository.removeService(info.getServiceId());
+
+                if (mRecordRepository.getServicesCount() == 0) {
+                    destroyNow();
+                }
+            }
+        }
+    }
+
+    /**
+     * Dependencies for {@link MdnsInterfaceAdvertiser}, useful for testing.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        /** @see MdnsRecordRepository */
+        @NonNull
+        public MdnsRecordRepository makeRecordRepository(@NonNull Looper looper,
+                @NonNull String[] deviceHostName) {
+            return new MdnsRecordRepository(looper, deviceHostName);
+        }
+
+        /** @see MdnsReplySender */
+        @NonNull
+        public MdnsReplySender makeReplySender(@NonNull String interfaceTag, @NonNull Looper looper,
+                @NonNull MdnsInterfaceSocket socket, @NonNull byte[] packetCreationBuffer) {
+            return new MdnsReplySender(interfaceTag, looper, socket, packetCreationBuffer);
+        }
+
+        /** @see MdnsAnnouncer */
+        public MdnsAnnouncer makeMdnsAnnouncer(@NonNull String interfaceTag, @NonNull Looper looper,
+                @NonNull MdnsReplySender replySender,
+                @Nullable PacketRepeaterCallback<MdnsAnnouncer.BaseAnnouncementInfo> cb) {
+            return new MdnsAnnouncer(interfaceTag, looper, replySender, cb);
+        }
+
+        /** @see MdnsProber */
+        public MdnsProber makeMdnsProber(@NonNull String interfaceTag, @NonNull Looper looper,
+                @NonNull MdnsReplySender replySender,
+                @NonNull PacketRepeaterCallback<MdnsProber.ProbingInfo> cb) {
+            return new MdnsProber(interfaceTag, looper, replySender, cb);
+        }
+    }
+
+    public MdnsInterfaceAdvertiser(@NonNull MdnsInterfaceSocket socket,
+            @NonNull List<LinkAddress> initialAddresses, @NonNull Looper looper,
+            @NonNull byte[] packetCreationBuffer, @NonNull Callback cb,
+            @NonNull String[] deviceHostName, @NonNull SharedLog sharedLog) {
+        this(socket, initialAddresses, looper, packetCreationBuffer, cb,
+                new Dependencies(), deviceHostName, sharedLog);
+    }
+
+    public MdnsInterfaceAdvertiser(@NonNull MdnsInterfaceSocket socket,
+            @NonNull List<LinkAddress> initialAddresses, @NonNull Looper looper,
+            @NonNull byte[] packetCreationBuffer, @NonNull Callback cb, @NonNull Dependencies deps,
+            @NonNull String[] deviceHostName, @NonNull SharedLog sharedLog) {
+        mTag = MdnsInterfaceAdvertiser.class.getSimpleName() + "/" + sharedLog.getTag();
+        mRecordRepository = deps.makeRecordRepository(looper, deviceHostName);
+        mRecordRepository.updateAddresses(initialAddresses);
+        mSocket = socket;
+        mCb = cb;
+        mCbHandler = new Handler(looper);
+        mReplySender = deps.makeReplySender(sharedLog.getTag(), looper, socket,
+                packetCreationBuffer);
+        mAnnouncer = deps.makeMdnsAnnouncer(sharedLog.getTag(), looper, mReplySender,
+                mAnnouncingCallback);
+        mProber = deps.makeMdnsProber(sharedLog.getTag(), looper, mReplySender, mProbingCallback);
+        mSharedLog = sharedLog;
+    }
+
+    /**
+     * Start the advertiser.
+     *
+     * The advertiser will stop itself when all services are removed and exit announcements sent,
+     * notifying via {@link Callback#onDestroyed}. This can also be triggered manually via
+     * {@link #destroyNow()}.
+     */
+    public void start() {
+        mSocket.addPacketHandler(this);
+    }
+
+    /**
+     * Start advertising a service.
+     *
+     * @throws NameConflictException There is already a service being advertised with that name.
+     */
+    public void addService(int id, NsdServiceInfo service, @Nullable String subtype)
+            throws NameConflictException {
+        final int replacedExitingService = mRecordRepository.addService(id, service, subtype);
+        // Cancel announcements for the existing service. This only happens for exiting services
+        // (so cancelling exiting announcements), as per RecordRepository.addService.
+        if (replacedExitingService >= 0) {
+            mSharedLog.i("Service " + replacedExitingService
+                    + " getting re-added, cancelling exit announcements");
+            mAnnouncer.stop(replacedExitingService);
+        }
+        mProber.startProbing(mRecordRepository.setServiceProbing(id));
+    }
+
+    /**
+     * Stop advertising a service.
+     *
+     * This will trigger exit announcements for the service.
+     */
+    public void removeService(int id) {
+        if (!mRecordRepository.hasActiveService(id)) return;
+        mProber.stop(id);
+        mAnnouncer.stop(id);
+        final MdnsAnnouncer.ExitAnnouncementInfo exitInfo = mRecordRepository.exitService(id);
+        if (exitInfo != null) {
+            // This effectively schedules destroyNow(), as it is to be called when the exit
+            // announcement finishes if there is no service left.
+            // A non-zero exit announcement delay follows legacy mdnsresponder behavior, and is
+            // also useful to ensure that when a host receives the exit announcement, the service
+            // has been unregistered on all interfaces; so an announcement sent from interface A
+            // that was already in-flight while unregistering won't be received after the exit on
+            // interface B.
+            mAnnouncer.startSending(id, exitInfo, EXIT_ANNOUNCEMENT_DELAY_MS);
+        } else {
+            // No exit announcement necessary: remove the service immediately.
+            mRecordRepository.removeService(id);
+            if (mRecordRepository.getServicesCount() == 0) {
+                destroyNow();
+            }
+        }
+    }
+
+    /**
+     * Update interface addresses used to advertise.
+     *
+     * This causes new address records to be announced.
+     */
+    public void updateAddresses(@NonNull List<LinkAddress> newAddresses) {
+        mRecordRepository.updateAddresses(newAddresses);
+        // TODO: restart advertising, but figure out what exit messages need to be sent for the
+        // previous addresses
+    }
+
+    /**
+     * Destroy the advertiser immediately, not sending any exit announcement.
+     *
+     * <p>Useful when the underlying network went away. This will trigger an onDestroyed callback.
+     */
+    public void destroyNow() {
+        for (int serviceId : mRecordRepository.clearServices()) {
+            mProber.stop(serviceId);
+            mAnnouncer.stop(serviceId);
+        }
+        mReplySender.cancelAll();
+        mSocket.removePacketHandler(this);
+        mCbHandler.post(() -> mCb.onDestroyed(mSocket));
+    }
+
+    /**
+     * Reset a service to the probing state due to a conflict found on the network.
+     */
+    public void restartProbingForConflict(int serviceId) {
+        final MdnsProber.ProbingInfo probingInfo = mRecordRepository.setServiceProbing(serviceId);
+        if (probingInfo == null) return;
+
+        mProber.restartForConflict(probingInfo);
+    }
+
+    /**
+     * Rename a service following a conflict found on the network, and restart probing.
+     *
+     * If the service was not registered on this {@link MdnsInterfaceAdvertiser}, this is a no-op.
+     */
+    public void renameServiceForConflict(int serviceId, NsdServiceInfo newInfo) {
+        final MdnsProber.ProbingInfo probingInfo = mRecordRepository.renameServiceForConflict(
+                serviceId, newInfo);
+        if (probingInfo == null) return;
+
+        mProber.restartForConflict(probingInfo);
+    }
+
+    /**
+     * Indicates whether probing is in progress for the given service on this interface.
+     *
+     * Also returns false if the specified service is not registered.
+     */
+    public boolean isProbing(int serviceId) {
+        return mRecordRepository.isProbing(serviceId);
+    }
+
+    @Override
+    public void handlePacket(byte[] recvbuf, int length, InetSocketAddress src) {
+        final MdnsPacket packet;
+        try {
+            packet = MdnsPacket.parse(new MdnsPacketReader(recvbuf, length));
+        } catch (MdnsPacket.ParseException e) {
+            Log.e(mTag, "Error parsing mDNS packet", e);
+            if (DBG) {
+                Log.v(
+                        mTag, "Packet: " + HexDump.toHexString(recvbuf, 0, length));
+            }
+            return;
+        }
+
+        if (DBG) {
+            Log.v(mTag,
+                    "Parsed packet with " + packet.questions.size() + " questions, "
+                            + packet.answers.size() + " answers, "
+                            + packet.authorityRecords.size() + " authority, "
+                            + packet.additionalRecords.size() + " additional from " + src);
+        }
+
+        for (int conflictServiceId : mRecordRepository.getConflictingServices(packet)) {
+            mCbHandler.post(() -> mCb.onServiceConflict(this, conflictServiceId));
+        }
+
+        // Even in case of conflict, add replies for other services. But in general conflicts would
+        // happen when the incoming packet has answer records (not a question), so there will be no
+        // answer. One exception is simultaneous probe tiebreaking (rfc6762 8.2), in which case the
+        // conflicting service is still probing and won't reply either.
+        final MdnsRecordRepository.ReplyInfo answers = mRecordRepository.getReply(packet, src);
+
+        if (answers == null) return;
+        mReplySender.queueReply(answers);
+    }
+}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
similarity index 66%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
index 6090415..119c7a8 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
@@ -22,10 +22,15 @@
 import android.annotation.NonNull;
 import android.net.LinkAddress;
 import android.net.util.SocketUtils;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.ParcelFileDescriptor;
 import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
 import android.util.Log;
 
+import java.io.FileDescriptor;
 import java.io.IOException;
 import java.net.DatagramPacket;
 import java.net.InetSocketAddress;
@@ -41,15 +46,19 @@
  * otherwise.
  *
  * @see MulticastSocket for javadoc of each public method.
+ * @see MulticastSocket for javadoc of each public method.
  */
 public class MdnsInterfaceSocket {
     private static final String TAG = MdnsInterfaceSocket.class.getSimpleName();
     @NonNull private final MulticastSocket mMulticastSocket;
     @NonNull private final NetworkInterface mNetworkInterface;
+    @NonNull private final MulticastPacketReader mPacketReader;
+    @NonNull private final ParcelFileDescriptor mFileDescriptor;
     private boolean mJoinedIpv4 = false;
     private boolean mJoinedIpv6 = false;
 
-    public MdnsInterfaceSocket(@NonNull NetworkInterface networkInterface, int port)
+    public MdnsInterfaceSocket(@NonNull NetworkInterface networkInterface, int port,
+            @NonNull Looper looper, @NonNull byte[] packetReadBuffer)
             throws IOException {
         mNetworkInterface = networkInterface;
         mMulticastSocket = new MulticastSocket(port);
@@ -58,11 +67,19 @@
         mMulticastSocket.setNetworkInterface(networkInterface);
 
         // Bind socket to the interface for receiving from that interface only.
-        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(mMulticastSocket)) {
-            SocketUtils.bindSocketToInterface(pfd.getFileDescriptor(), mNetworkInterface.getName());
+        mFileDescriptor = ParcelFileDescriptor.fromDatagramSocket(mMulticastSocket);
+        try {
+            final FileDescriptor fd = mFileDescriptor.getFileDescriptor();
+            final int flags = Os.fcntlInt(fd, OsConstants.F_GETFL, 0);
+            Os.fcntlInt(fd, OsConstants.F_SETFL, flags | OsConstants.SOCK_NONBLOCK);
+            SocketUtils.bindSocketToInterface(fd, mNetworkInterface.getName());
         } catch (ErrnoException e) {
             throw new IOException("Error setting socket options", e);
         }
+
+        mPacketReader = new MulticastPacketReader(networkInterface.getName(), mFileDescriptor,
+                new Handler(looper), packetReadBuffer);
+        mPacketReader.start();
     }
 
     /**
@@ -74,23 +91,14 @@
         mMulticastSocket.send(packet);
     }
 
-    /**
-     * Receives a datagram packet from this socket.
-     *
-     * <p>This method could be used on any thread.
-     */
-    public void receive(@NonNull DatagramPacket packet) throws IOException {
-        mMulticastSocket.receive(packet);
-    }
-
-    private boolean hasIpv4Address(List<LinkAddress> addresses) {
+    private static boolean hasIpv4Address(@NonNull List<LinkAddress> addresses) {
         for (LinkAddress address : addresses) {
             if (address.isIpv4()) return true;
         }
         return false;
     }
 
-    private boolean hasIpv6Address(List<LinkAddress> addresses) {
+    private static boolean hasIpv6Address(@NonNull List<LinkAddress> addresses) {
         for (LinkAddress address : addresses) {
             if (address.isIpv6()) return true;
         }
@@ -103,7 +111,7 @@
         maybeJoinIpv6(addresses);
     }
 
-    private boolean joinGroup(InetSocketAddress multicastAddress) {
+    private boolean joinGroup(@NonNull InetSocketAddress multicastAddress) {
         try {
             mMulticastSocket.joinGroup(multicastAddress, mNetworkInterface);
             return true;
@@ -114,7 +122,7 @@
         }
     }
 
-    private void maybeJoinIpv4(List<LinkAddress> addresses) {
+    private void maybeJoinIpv4(@NonNull List<LinkAddress> addresses) {
         final boolean hasAddr = hasIpv4Address(addresses);
         if (!mJoinedIpv4 && hasAddr) {
             mJoinedIpv4 = joinGroup(MULTICAST_IPV4_ADDRESS);
@@ -124,7 +132,7 @@
         }
     }
 
-    private void maybeJoinIpv6(List<LinkAddress> addresses) {
+    private void maybeJoinIpv6(@NonNull List<LinkAddress> addresses) {
         final boolean hasAddr = hasIpv6Address(addresses);
         if (!mJoinedIpv6 && hasAddr) {
             mJoinedIpv6 = joinGroup(MULTICAST_IPV6_ADDRESS);
@@ -134,33 +142,40 @@
         }
     }
 
-    /*** Destroy this socket by leaving all joined multicast groups and closing this socket. */
+    /*** Destroy the socket */
     public void destroy() {
-        if (mJoinedIpv4) {
-            try {
-                mMulticastSocket.leaveGroup(MULTICAST_IPV4_ADDRESS, mNetworkInterface);
-            } catch (IOException e) {
-                Log.e(TAG, "Error leaving IPv4 group for " + mNetworkInterface, e);
-            }
-        }
-        if (mJoinedIpv6) {
-            try {
-                mMulticastSocket.leaveGroup(MULTICAST_IPV6_ADDRESS, mNetworkInterface);
-            } catch (IOException e) {
-                Log.e(TAG, "Error leaving IPv4 group for " + mNetworkInterface, e);
-            }
+        mPacketReader.stop();
+        try {
+            mFileDescriptor.close();
+        } catch (IOException e) {
+            Log.e(TAG, "Close file descriptor failed.");
         }
         mMulticastSocket.close();
     }
 
     /**
-     * Returns the index of the network interface that this socket is bound to. If the interface
-     * cannot be determined, returns -1.
+     * Add a handler to receive callbacks when reads the packet from socket. If the handler is
+     * already set, this is a no-op.
+     */
+    public void addPacketHandler(@NonNull MulticastPacketReader.PacketHandler handler) {
+        mPacketReader.addPacketHandler(handler);
+    }
+
+    /**
+     * Remove a handler added via {@link #addPacketHandler}. If the handler is not present, this is
+     * a no-op.
+     */
+    public void removePacketHandler(@NonNull MulticastPacketReader.PacketHandler handler) {
+        mPacketReader.removePacketHandler(handler);
+    }
+
+    /**
+     * Returns the network interface that this socket is bound to.
      *
      * <p>This method could be used on any thread.
      */
-    public int getInterfaceIndex() {
-        return mNetworkInterface.getIndex();
+    public NetworkInterface getInterface() {
+        return mNetworkInterface;
     }
 
     /*** Returns whether this socket has joined IPv4 group */
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
new file mode 100644
index 0000000..d9bfb26
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
@@ -0,0 +1,277 @@
+/*
+ * 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 com.android.server.connectivity.mdns;
+
+import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.LinkAddress;
+import android.net.Network;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetSocketAddress;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The {@link MdnsMultinetworkSocketClient} manages the multinetwork socket for mDns
+ *
+ *  * <p>This class is not thread safe.
+ */
+public class MdnsMultinetworkSocketClient implements MdnsSocketClientBase {
+    private static final String TAG = MdnsMultinetworkSocketClient.class.getSimpleName();
+    private static final boolean DBG = MdnsDiscoveryManager.DBG;
+
+    @NonNull private final Handler mHandler;
+    @NonNull private final MdnsSocketProvider mSocketProvider;
+
+    private final ArrayMap<MdnsServiceBrowserListener, InterfaceSocketCallback> mRequestedNetworks =
+            new ArrayMap<>();
+    private final ArrayMap<MdnsInterfaceSocket, ReadPacketHandler> mSocketPacketHandlers =
+            new ArrayMap<>();
+    private MdnsSocketClientBase.Callback mCallback = null;
+    private int mReceivedPacketNumber = 0;
+
+    public MdnsMultinetworkSocketClient(@NonNull Looper looper,
+            @NonNull MdnsSocketProvider provider) {
+        mHandler = new Handler(looper);
+        mSocketProvider = provider;
+    }
+
+    private class InterfaceSocketCallback implements MdnsSocketProvider.SocketCallback {
+        @NonNull
+        private final SocketCreationCallback mSocketCreationCallback;
+        @NonNull
+        private final ArrayMap<MdnsInterfaceSocket, Network> mActiveNetworkSockets =
+                new ArrayMap<>();
+
+        InterfaceSocketCallback(SocketCreationCallback socketCreationCallback) {
+            mSocketCreationCallback = socketCreationCallback;
+        }
+
+        @Override
+        public void onSocketCreated(@Nullable Network network,
+                @NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> addresses) {
+            // The socket may be already created by other request before, try to get the stored
+            // ReadPacketHandler.
+            ReadPacketHandler handler = mSocketPacketHandlers.get(socket);
+            if (handler == null) {
+                // First request to create this socket. Initial a ReadPacketHandler for this socket.
+                handler = new ReadPacketHandler(network, socket.getInterface().getIndex());
+                mSocketPacketHandlers.put(socket, handler);
+            }
+            socket.addPacketHandler(handler);
+            mActiveNetworkSockets.put(socket, network);
+            mSocketCreationCallback.onSocketCreated(network);
+        }
+
+        @Override
+        public void onInterfaceDestroyed(@Nullable Network network,
+                @NonNull MdnsInterfaceSocket socket) {
+            notifySocketDestroyed(socket);
+            maybeCleanupPacketHandler(socket);
+        }
+
+        private void notifySocketDestroyed(@NonNull MdnsInterfaceSocket socket) {
+            final Network network = mActiveNetworkSockets.remove(socket);
+            if (!isAnySocketActive(network)) {
+                mSocketCreationCallback.onAllSocketsDestroyed(network);
+            }
+        }
+
+        void onNetworkUnrequested() {
+            for (int i = mActiveNetworkSockets.size() - 1; i >= 0; i--) {
+                // Iterate from the end so the socket can be removed
+                final MdnsInterfaceSocket socket = mActiveNetworkSockets.keyAt(i);
+                notifySocketDestroyed(socket);
+                maybeCleanupPacketHandler(socket);
+            }
+        }
+    }
+
+    private boolean isSocketActive(@NonNull MdnsInterfaceSocket socket) {
+        for (int i = 0; i < mRequestedNetworks.size(); i++) {
+            final InterfaceSocketCallback isc = mRequestedNetworks.valueAt(i);
+            if (isc.mActiveNetworkSockets.containsKey(socket)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isAnySocketActive(@Nullable Network network) {
+        for (int i = 0; i < mRequestedNetworks.size(); i++) {
+            final InterfaceSocketCallback isc = mRequestedNetworks.valueAt(i);
+            if (isc.mActiveNetworkSockets.containsValue(network)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private ArrayMap<MdnsInterfaceSocket, Network> getActiveSockets() {
+        final ArrayMap<MdnsInterfaceSocket, Network> sockets = new ArrayMap<>();
+        for (int i = 0; i < mRequestedNetworks.size(); i++) {
+            final InterfaceSocketCallback isc = mRequestedNetworks.valueAt(i);
+            sockets.putAll(isc.mActiveNetworkSockets);
+        }
+        return sockets;
+    }
+
+    private void maybeCleanupPacketHandler(@NonNull MdnsInterfaceSocket socket) {
+        if (isSocketActive(socket)) return;
+        mSocketPacketHandlers.remove(socket);
+    }
+
+    private class ReadPacketHandler implements MulticastPacketReader.PacketHandler {
+        private final Network mNetwork;
+        private final int mInterfaceIndex;
+
+        ReadPacketHandler(@NonNull Network network, int interfaceIndex) {
+            mNetwork = network;
+            mInterfaceIndex = interfaceIndex;
+        }
+
+        @Override
+        public void handlePacket(byte[] recvbuf, int length, InetSocketAddress src) {
+            processResponsePacket(recvbuf, length, mInterfaceIndex, mNetwork);
+        }
+    }
+
+    /*** Set callback for receiving mDns response */
+    @Override
+    public void setCallback(@Nullable MdnsSocketClientBase.Callback callback) {
+        ensureRunningOnHandlerThread(mHandler);
+        mCallback = callback;
+    }
+
+    /***
+     * Notify that the given network is requested for mdns discovery / resolution
+     *
+     * @param listener the listener for discovery.
+     * @param network the target network for discovery. Null means discovery on all possible
+     *                interfaces.
+     * @param socketCreationCallback the callback to notify socket creation.
+     */
+    @Override
+    public void notifyNetworkRequested(@NonNull MdnsServiceBrowserListener listener,
+            @Nullable Network network, @NonNull SocketCreationCallback socketCreationCallback) {
+        ensureRunningOnHandlerThread(mHandler);
+        InterfaceSocketCallback callback = mRequestedNetworks.get(listener);
+        if (callback != null) {
+            throw new IllegalArgumentException("Can not register duplicated listener");
+        }
+
+        if (DBG) Log.d(TAG, "notifyNetworkRequested: network=" + network);
+        callback = new InterfaceSocketCallback(socketCreationCallback);
+        mRequestedNetworks.put(listener, callback);
+        mSocketProvider.requestSocket(network, callback);
+    }
+
+    /*** Notify that the network is unrequested */
+    @Override
+    public void notifyNetworkUnrequested(@NonNull MdnsServiceBrowserListener listener) {
+        ensureRunningOnHandlerThread(mHandler);
+        final InterfaceSocketCallback callback = mRequestedNetworks.get(listener);
+        if (callback == null) {
+            Log.e(TAG, "Can not be unrequested with unknown listener=" + listener);
+            return;
+        }
+        callback.onNetworkUnrequested();
+        // onNetworkUnrequested does cleanups based on mRequestedNetworks, only remove afterwards
+        mRequestedNetworks.remove(listener);
+        mSocketProvider.unrequestSocket(callback);
+    }
+
+    @Override
+    public Looper getLooper() {
+        return mHandler.getLooper();
+    }
+
+    private void sendMdnsPacket(@NonNull DatagramPacket packet, @Nullable Network targetNetwork) {
+        final boolean isIpv6 = ((InetSocketAddress) packet.getSocketAddress()).getAddress()
+                instanceof Inet6Address;
+        final boolean isIpv4 = ((InetSocketAddress) packet.getSocketAddress()).getAddress()
+                instanceof Inet4Address;
+        final ArrayMap<MdnsInterfaceSocket, Network> activeSockets = getActiveSockets();
+        for (int i = 0; i < activeSockets.size(); i++) {
+            final MdnsInterfaceSocket socket = activeSockets.keyAt(i);
+            final Network network = activeSockets.valueAt(i);
+            // Check ip capability and network before sending packet
+            if (((isIpv6 && socket.hasJoinedIpv6()) || (isIpv4 && socket.hasJoinedIpv4()))
+                    // Contrary to MdnsUtils.isNetworkMatched, only send packets targeting
+                    // the null network to interfaces that have the null network (tethering
+                    // downstream interfaces).
+                    && Objects.equals(network, targetNetwork)) {
+                try {
+                    socket.send(packet);
+                } catch (IOException e) {
+                    Log.e(TAG, "Failed to send a mDNS packet.", e);
+                }
+            }
+        }
+    }
+
+    private void processResponsePacket(byte[] recvbuf, int length, int interfaceIndex,
+            @NonNull Network network) {
+        int packetNumber = ++mReceivedPacketNumber;
+
+        final MdnsPacket response;
+        try {
+            response = MdnsResponseDecoder.parseResponse(recvbuf, length);
+        } catch (MdnsPacket.ParseException e) {
+            if (e.code != MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE) {
+                Log.e(TAG, e.getMessage(), e);
+                if (mCallback != null) {
+                    mCallback.onFailedToParseMdnsResponse(packetNumber, e.code, network);
+                }
+            }
+            return;
+        }
+
+        if (mCallback != null) {
+            mCallback.onResponseReceived(response, interfaceIndex, network);
+        }
+    }
+
+    /**
+     * Sends a mDNS request packet via given network that asks for multicast response. Null network
+     * means sending packet via all networks.
+     */
+    @Override
+    public void sendMulticastPacket(@NonNull DatagramPacket packet, @Nullable Network network) {
+        mHandler.post(() -> sendMdnsPacket(packet, network));
+    }
+
+    /**
+     * Sends a mDNS request packet via given network that asks for unicast response. Null network
+     * means sending packet via all networks.
+     */
+    @Override
+    public void sendUnicastPacket(@NonNull DatagramPacket packet, @Nullable Network network) {
+        // TODO: Separate unicast packet.
+        mHandler.post(() -> sendMdnsPacket(packet, network));
+    }
+}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsNsecRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsNsecRecord.java
similarity index 81%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsNsecRecord.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsNsecRecord.java
index 57c3c03..1239180 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsNsecRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsNsecRecord.java
@@ -17,12 +17,15 @@
 package com.android.server.connectivity.mdns;
 
 import android.net.DnsResolver;
+import android.text.TextUtils;
 
 import com.android.net.module.util.CollectionUtils;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
 
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Objects;
 
 /**
  * A mDNS "NSEC" record, used in particular for negative responses (RFC6762 6.1).
@@ -96,8 +99,9 @@
 
     @Override
     protected void writeData(MdnsPacketWriter writer) throws IOException {
-        // No compression as per RFC3845 2.1.1
-        writer.writeLabelsNoCompression(mNextDomain);
+        // Standard NSEC records should use no compression for the Next Domain Name field as per
+        // RFC3845 2.1.1, but for mDNS RFC6762 18.14 specifies that compression should be used.
+        writer.writeLabels(mNextDomain);
 
         // type bitmaps: RFC3845 2.1.2
         int typesBlockStart = 0;
@@ -139,4 +143,32 @@
         writer.writeUInt8(bytesInBlock);
         writer.writeBytes(bytes);
     }
+
+    @Override
+    public String toString() {
+        return "NSEC: NextDomain: " + TextUtils.join(".", mNextDomain)
+                + " Types " + Arrays.toString(mTypes);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(super.hashCode(),
+                Arrays.hashCode(MdnsUtils.toDnsLabelsLowerCase(mNextDomain)),
+                Arrays.hashCode(mTypes));
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (!(other instanceof MdnsNsecRecord)) {
+            return false;
+        }
+
+        return super.equals(other)
+                && MdnsUtils.equalsDnsLabelIgnoreDnsCase(mNextDomain,
+                ((MdnsNsecRecord) other).mNextDomain)
+                && Arrays.equals(mTypes, ((MdnsNsecRecord) other).mTypes);
+    }
 }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsPacket.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPacket.java
new file mode 100644
index 0000000..27002b9
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPacket.java
@@ -0,0 +1,231 @@
+/*
+ * 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 com.android.server.connectivity.mdns;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A class holding data that can be included in a mDNS packet.
+ */
+public class MdnsPacket {
+    private static final String TAG = MdnsPacket.class.getSimpleName();
+
+    public final int flags;
+    @NonNull
+    public final List<MdnsRecord> questions;
+    @NonNull
+    public final List<MdnsRecord> answers;
+    @NonNull
+    public final List<MdnsRecord> authorityRecords;
+    @NonNull
+    public final List<MdnsRecord> additionalRecords;
+
+    MdnsPacket(int flags,
+            @NonNull List<MdnsRecord> questions,
+            @NonNull List<MdnsRecord> answers,
+            @NonNull List<MdnsRecord> authorityRecords,
+            @NonNull List<MdnsRecord> additionalRecords) {
+        this.flags = flags;
+        this.questions = Collections.unmodifiableList(questions);
+        this.answers = Collections.unmodifiableList(answers);
+        this.authorityRecords = Collections.unmodifiableList(authorityRecords);
+        this.additionalRecords = Collections.unmodifiableList(additionalRecords);
+    }
+
+    /**
+     * Exception thrown on parse errors.
+     */
+    public static class ParseException extends IOException {
+        public final int code;
+
+        public ParseException(int code, @NonNull String message, @Nullable Throwable cause) {
+            super(message, cause);
+            this.code = code;
+        }
+    }
+
+    /**
+     * Parse the packet in the provided {@link MdnsPacketReader}.
+     */
+    @NonNull
+    public static MdnsPacket parse(@NonNull MdnsPacketReader reader) throws ParseException {
+        final int flags;
+        try {
+            reader.readUInt16(); // transaction ID (not used)
+            flags = reader.readUInt16();
+        } catch (EOFException e) {
+            throw new ParseException(MdnsResponseErrorCode.ERROR_END_OF_FILE,
+                    "Reached the end of the mDNS response unexpectedly.", e);
+        }
+        return parseRecordsSection(reader, flags);
+    }
+
+    /**
+     * Parse the records section of a mDNS packet in the provided {@link MdnsPacketReader}.
+     *
+     * The records section starts with the questions count, just after the packet flags.
+     */
+    public static MdnsPacket parseRecordsSection(@NonNull MdnsPacketReader reader, int flags)
+            throws ParseException {
+        try {
+            final int numQuestions = reader.readUInt16();
+            final int numAnswers = reader.readUInt16();
+            final int numAuthority = reader.readUInt16();
+            final int numAdditional = reader.readUInt16();
+
+            final ArrayList<MdnsRecord> questions = parseRecords(reader, numQuestions, true);
+            final ArrayList<MdnsRecord> answers = parseRecords(reader, numAnswers, false);
+            final ArrayList<MdnsRecord> authority = parseRecords(reader, numAuthority, false);
+            final ArrayList<MdnsRecord> additional = parseRecords(reader, numAdditional, false);
+
+            return new MdnsPacket(flags, questions, answers, authority, additional);
+        } catch (EOFException e) {
+            throw new ParseException(MdnsResponseErrorCode.ERROR_END_OF_FILE,
+                    "Reached the end of the mDNS response unexpectedly.", e);
+        }
+    }
+
+    private static ArrayList<MdnsRecord> parseRecords(@NonNull MdnsPacketReader reader, int count,
+            boolean isQuestion)
+            throws ParseException {
+        final ArrayList<MdnsRecord> records = new ArrayList<>(count);
+        for (int i = 0; i < count; ++i) {
+            final MdnsRecord record = parseRecord(reader, isQuestion);
+            if (record != null) {
+                records.add(record);
+            }
+        }
+        return records;
+    }
+
+    @Nullable
+    private static MdnsRecord parseRecord(@NonNull MdnsPacketReader reader, boolean isQuestion)
+            throws ParseException {
+        String[] name;
+        try {
+            name = reader.readLabels();
+        } catch (IOException e) {
+            throw new ParseException(MdnsResponseErrorCode.ERROR_READING_RECORD_NAME,
+                    "Failed to read labels from mDNS response.", e);
+        }
+
+        final int type;
+        try {
+            type = reader.readUInt16();
+        } catch (EOFException e) {
+            throw new ParseException(MdnsResponseErrorCode.ERROR_END_OF_FILE,
+                    "Reached the end of the mDNS response unexpectedly.", e);
+        }
+
+        switch (type) {
+            case MdnsRecord.TYPE_A: {
+                try {
+                    return new MdnsInetAddressRecord(name, MdnsRecord.TYPE_A, reader, isQuestion);
+                } catch (IOException e) {
+                    throw new ParseException(MdnsResponseErrorCode.ERROR_READING_A_RDATA,
+                            "Failed to read A record from mDNS response.", e);
+                }
+            }
+
+            case MdnsRecord.TYPE_AAAA: {
+                try {
+                    return new MdnsInetAddressRecord(name,
+                            MdnsRecord.TYPE_AAAA, reader, isQuestion);
+                } catch (IOException e) {
+                    throw new ParseException(MdnsResponseErrorCode.ERROR_READING_AAAA_RDATA,
+                            "Failed to read AAAA record from mDNS response.", e);
+                }
+            }
+
+            case MdnsRecord.TYPE_PTR: {
+                try {
+                    return new MdnsPointerRecord(name, reader, isQuestion);
+                } catch (IOException e) {
+                    throw new ParseException(MdnsResponseErrorCode.ERROR_READING_PTR_RDATA,
+                            "Failed to read PTR record from mDNS response.", e);
+                }
+            }
+
+            case MdnsRecord.TYPE_SRV: {
+                try {
+                    return new MdnsServiceRecord(name, reader, isQuestion);
+                } catch (IOException e) {
+                    throw new ParseException(MdnsResponseErrorCode.ERROR_READING_SRV_RDATA,
+                            "Failed to read SRV record from mDNS response.", e);
+                }
+            }
+
+            case MdnsRecord.TYPE_TXT: {
+                try {
+                    return new MdnsTextRecord(name, reader, isQuestion);
+                } catch (IOException e) {
+                    throw new ParseException(MdnsResponseErrorCode.ERROR_READING_TXT_RDATA,
+                            "Failed to read TXT record from mDNS response.", e);
+                }
+            }
+
+            case MdnsRecord.TYPE_NSEC: {
+                try {
+                    return new MdnsNsecRecord(name, reader, isQuestion);
+                } catch (IOException e) {
+                    throw new ParseException(MdnsResponseErrorCode.ERROR_READING_NSEC_RDATA,
+                            "Failed to read NSEC record from mDNS response.", e);
+                }
+            }
+
+            case MdnsRecord.TYPE_ANY: {
+                try {
+                    return new MdnsAnyRecord(name, reader);
+                } catch (IOException e) {
+                    throw new ParseException(MdnsResponseErrorCode.ERROR_READING_ANY_RDATA,
+                            "Failed to read TYPE_ANY record from mDNS response.", e);
+                }
+            }
+
+            default: {
+                try {
+                    if (MdnsAdvertiser.DBG) {
+                        Log.i(TAG, "Skipping parsing of record of unhandled type " + type);
+                    }
+                    skipMdnsRecord(reader, isQuestion);
+                    return null;
+                } catch (IOException e) {
+                    throw new ParseException(MdnsResponseErrorCode.ERROR_SKIPPING_UNKNOWN_RECORD,
+                            "Failed to skip mDNS record.", e);
+                }
+            }
+        }
+    }
+
+    private static void skipMdnsRecord(@NonNull MdnsPacketReader reader, boolean isQuestion)
+            throws IOException {
+        reader.skip(2); // Skip the class
+        if (isQuestion) return;
+        // Skip TTL and data
+        reader.skip(4);
+        int dataLength = reader.readUInt16();
+        reader.skip(dataLength);
+    }
+}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsPacketReader.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketReader.java
similarity index 97%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsPacketReader.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsPacketReader.java
index 856a2cd..aa38844 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsPacketReader.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketReader.java
@@ -38,8 +38,13 @@
 
     /** Constructs a reader for the given packet. */
     public MdnsPacketReader(DatagramPacket packet) {
-        buf = packet.getData();
-        count = packet.getLength();
+        this(packet.getData(), packet.getLength());
+    }
+
+    /** Constructs a reader for the given packet. */
+    public MdnsPacketReader(byte[] buffer, int length) {
+        buf = buffer;
+        count = length;
         pos = 0;
         limit = -1;
         labelDictionary = new SparseArray<>(16);
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsPacketRepeater.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
similarity index 88%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
index 015dbd8..4c385da 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketRepeater.java
@@ -16,6 +16,9 @@
 
 package com.android.server.connectivity.mdns;
 
+import static com.android.server.connectivity.mdns.MdnsRecordRepository.IPV4_ADDR;
+import static com.android.server.connectivity.mdns.MdnsRecordRepository.IPV6_ADDR;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Handler;
@@ -24,7 +27,7 @@
 import android.util.Log;
 
 import java.io.IOException;
-import java.net.SocketAddress;
+import java.net.InetSocketAddress;
 
 /**
  * A class used to send several packets at given time intervals.
@@ -32,6 +35,10 @@
  */
 public abstract class MdnsPacketRepeater<T extends MdnsPacketRepeater.Request> {
     private static final boolean DBG = MdnsAdvertiser.DBG;
+    private static final InetSocketAddress[] ALL_ADDRS = new InetSocketAddress[] {
+            IPV4_ADDR, IPV6_ADDR
+    };
+
     @NonNull
     private final MdnsReplySender mReplySender;
     @NonNull
@@ -70,12 +77,6 @@
         MdnsPacket getPacket(int index);
 
         /**
-         * Get a set of destinations for the packet for one iteration.
-         */
-        @NonNull
-        Iterable<SocketAddress> getDestinations(int index);
-
-        /**
          * Get the delay in milliseconds until the next packet transmission.
          */
         long getDelayMs(int nextIndex);
@@ -110,12 +111,13 @@
             }
 
             final MdnsPacket packet = request.getPacket(index);
-            final Iterable<SocketAddress> destinations = request.getDestinations(index);
             if (DBG) {
-                Log.v(getTag(), "Sending packets to " + destinations + " for iteration "
-                        + index + " out of " + request.getNumSends());
+                Log.v(getTag(), "Sending packets for iteration " + index + " out of "
+                        + request.getNumSends() + " for ID " + msg.what);
             }
-            for (SocketAddress destination : destinations) {
+            // Send to both v4 and v6 addresses; the reply sender will take care of ignoring the
+            // send when the socket has not joined the relevant group.
+            for (InetSocketAddress destination : ALL_ADDRS) {
                 try {
                     mReplySender.sendNow(packet, destination);
                 } catch (IOException e) {
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsPacketWriter.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketWriter.java
similarity index 86%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsPacketWriter.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsPacketWriter.java
index 1f22fa9..6879a64 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsPacketWriter.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPacketWriter.java
@@ -17,11 +17,11 @@
 package com.android.server.connectivity.mdns;
 
 import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
 
 import java.io.IOException;
 import java.net.DatagramPacket;
 import java.net.SocketAddress;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -180,7 +180,7 @@
             int existingOffset = entry.getKey();
             String[] existingLabels = entry.getValue();
 
-            if (Arrays.equals(existingLabels, labels)) {
+            if (MdnsUtils.equalsDnsLabelIgnoreDnsCase(existingLabels, labels)) {
                 writePointer(existingOffset);
                 return;
             } else if (MdnsRecord.labelsAreSuffix(existingLabels, labels)) {
@@ -192,22 +192,31 @@
             }
         }
 
+        final int[] offsets;
         if (suffixLength > 0) {
-            for (int i = 0; i < (labels.length - suffixLength); ++i) {
-                writeString(labels[i]);
-            }
+            offsets = writePartialLabelsNoCompression(labels, labels.length - suffixLength);
             writePointer(suffixPointer);
         } else {
-            int[] offsets = writeLabelsNoCompression(labels);
-
-            // Add entries to the label dictionary for each suffix of the label list, including
-            // the whole list itself.
-            for (int i = 0, len = labels.length; i < labels.length; ++i, --len) {
-                String[] value = new String[len];
-                System.arraycopy(labels, i, value, 0, len);
-                labelDictionary.put(offsets[i], value);
-            }
+            offsets = writeLabelsNoCompression(labels);
         }
+
+        // Add entries to the label dictionary for each suffix of the label list, including
+        // the whole list itself.
+        // Do not replace the last suffixLength suffixes that already have dictionary entries.
+        for (int i = 0, len = labels.length; i < labels.length - suffixLength; ++i, --len) {
+            String[] value = new String[len];
+            System.arraycopy(labels, i, value, 0, len);
+            labelDictionary.put(offsets[i], value);
+        }
+    }
+
+    private int[] writePartialLabelsNoCompression(String[] labels, int count) throws IOException {
+        int[] offsets = new int[count];
+        for (int i = 0; i < count; ++i) {
+            offsets[i] = getWritePosition();
+            writeString(labels[i]);
+        }
+        return offsets;
     }
 
     /**
@@ -216,11 +225,7 @@
      * @return The offsets where each label was written to.
      */
     public int[] writeLabelsNoCompression(String[] labels) throws IOException {
-        int[] offsets = new int[labels.length];
-        for (int i = 0; i < labels.length; ++i) {
-            offsets[i] = getWritePosition();
-            writeString(labels[i]);
-        }
+        final int[] offsets = writePartialLabelsNoCompression(labels, labels.length);
         writeUInt8(0); // NUL terminator
         return offsets;
     }
@@ -246,4 +251,4 @@
     public DatagramPacket getPacket(SocketAddress destAddress) throws IOException {
         return new DatagramPacket(data, pos, destAddress);
     }
-}
\ No newline at end of file
+}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsPointerRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPointerRecord.java
similarity index 85%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsPointerRecord.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsPointerRecord.java
index 2c7b26b..c88ead0 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsPointerRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPointerRecord.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
 
 import java.io.IOException;
 import java.util.Arrays;
@@ -60,7 +61,8 @@
     }
 
     public boolean hasSubtype() {
-        return (name != null) && (name.length > 2) && name[1].equals(MdnsConstants.SUBTYPE_LABEL);
+        return (name != null) && (name.length > 2) && MdnsUtils.equalsIgnoreDnsCase(name[1],
+                MdnsConstants.SUBTYPE_LABEL);
     }
 
     public String getSubtype() {
@@ -74,7 +76,7 @@
 
     @Override
     public int hashCode() {
-        return (super.hashCode() * 31) + Arrays.hashCode(pointer);
+        return (super.hashCode() * 31) + Arrays.hashCode(MdnsUtils.toDnsLabelsLowerCase(pointer));
     }
 
     @Override
@@ -86,6 +88,7 @@
             return false;
         }
 
-        return super.equals(other) && Arrays.equals(pointer, ((MdnsPointerRecord) other).pointer);
+        return super.equals(other) && MdnsUtils.equalsDnsLabelIgnoreDnsCase(pointer,
+                ((MdnsPointerRecord) other).pointer);
     }
 }
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java b/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java
new file mode 100644
index 0000000..ecf846e
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsProber.java
@@ -0,0 +1,158 @@
+/*
+ * 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 com.android.server.connectivity.mdns;
+
+import android.annotation.NonNull;
+import android.os.Looper;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.CollectionUtils;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Sends mDns probe requests to verify service records are unique on the network.
+ *
+ * TODO: implement receiving replies and handling conflicts.
+ */
+public class MdnsProber extends MdnsPacketRepeater<MdnsProber.ProbingInfo> {
+    private static final long CONFLICT_RETRY_DELAY_MS = 5_000L;
+    @NonNull
+    private final String mLogTag;
+
+    public MdnsProber(@NonNull String interfaceTag, @NonNull Looper looper,
+            @NonNull MdnsReplySender replySender,
+            @NonNull PacketRepeaterCallback<ProbingInfo> cb) {
+        super(looper, replySender, cb);
+        mLogTag = MdnsProber.class.getSimpleName() + "/" + interfaceTag;
+    }
+
+    /** Probing request to send with {@link MdnsProber}. */
+    public static class ProbingInfo implements Request {
+
+        private final int mServiceId;
+        @NonNull
+        private final MdnsPacket mPacket;
+
+        /**
+         * Create a new ProbingInfo
+         * @param serviceId Service to probe for.
+         * @param probeRecords Records to be probed for uniqueness.
+         */
+        ProbingInfo(int serviceId, @NonNull List<MdnsRecord> probeRecords) {
+            mServiceId = serviceId;
+            mPacket = makePacket(probeRecords);
+        }
+
+        public int getServiceId() {
+            return mServiceId;
+        }
+
+        @NonNull
+        @Override
+        public MdnsPacket getPacket(int index) {
+            return mPacket;
+        }
+
+        @Override
+        public long getDelayMs(int nextIndex) {
+            // As per https://datatracker.ietf.org/doc/html/rfc6762#section-8.1
+            return 250L;
+        }
+
+        @Override
+        public int getNumSends() {
+            // 3 packets as per https://datatracker.ietf.org/doc/html/rfc6762#section-8.1
+            return 3;
+        }
+
+        private static MdnsPacket makePacket(@NonNull List<MdnsRecord> records) {
+            final ArrayList<MdnsRecord> questions = new ArrayList<>(records.size());
+            for (final MdnsRecord record : records) {
+                if (containsName(questions, record.getName())) {
+                    // Already added this name
+                    continue;
+                }
+
+                // TODO: legacy Android mDNS used to send the first probe (only) as unicast, even
+                //  though https://datatracker.ietf.org/doc/html/rfc6762#section-8.1 says they
+                // SHOULD all be. rfc6762 15.1 says that if the port is shared with another
+                // responder unicast questions should not be used, and the legacy mdnsresponder may
+                // be running, so not using unicast at all may be better. Consider using legacy
+                // behavior if this causes problems.
+                questions.add(new MdnsAnyRecord(record.getName(), false /* unicast */));
+            }
+
+            return new MdnsPacket(
+                    MdnsConstants.FLAGS_QUERY,
+                    questions,
+                    Collections.emptyList() /* answers */,
+                    records /* authorityRecords */,
+                    Collections.emptyList() /* additionalRecords */);
+        }
+
+        /**
+         * Return whether the specified name is present in the list of records.
+         */
+        private static boolean containsName(@NonNull List<MdnsRecord> records,
+                @NonNull String[] name) {
+            return CollectionUtils.any(records,
+                    r -> MdnsUtils.equalsDnsLabelIgnoreDnsCase(name, r.getName()));
+        }
+    }
+
+    @NonNull
+    @Override
+    protected String getTag() {
+        return mLogTag;
+    }
+
+    @VisibleForTesting
+    protected long getInitialDelay() {
+        // First wait for a random time in 0-250ms
+        // as per https://datatracker.ietf.org/doc/html/rfc6762#section-8.1
+        return (long) (Math.random() * 250);
+    }
+
+    /**
+     * Start sending packets for probing.
+     */
+    public void startProbing(@NonNull ProbingInfo info) {
+        startProbing(info, getInitialDelay());
+    }
+
+    private void startProbing(@NonNull ProbingInfo info, long delay) {
+        startSending(info.getServiceId(), info, delay);
+    }
+
+    /**
+     * Restart probing with new service info as a conflict was found.
+     */
+    public void restartForConflict(@NonNull ProbingInfo newInfo) {
+        stop(newInfo.getServiceId());
+
+        /* RFC 6762 8.1: "If fifteen conflicts occur within any ten-second period, then the host
+        MUST wait at least five seconds before each successive additional probe attempt. [...]
+        For very simple devices, a valid way to comply with this requirement is to always wait
+        five seconds after any failed probe attempt before trying again. */
+        // TODO: count 15 conflicts in 10s instead of waiting for 5s every time
+        startProbing(newInfo, CONFLICT_RETRY_DELAY_MS);
+    }
+}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecord.java
similarity index 91%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsRecord.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsRecord.java
index 10b8825..28bd1b4 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecord.java
@@ -24,6 +24,7 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
 
 import java.io.IOException;
 import java.util.Arrays;
@@ -45,6 +46,9 @@
     private static final int FLAG_CACHE_FLUSH = 0x8000;
 
     public static final long RECEIPT_TIME_NOT_SENT = 0L;
+    public static final int CLASS_ANY = 0x00ff;
+    /** Max label length as per RFC 1034/1035 */
+    public static final int MAX_LABEL_LENGTH = 63;
 
     /** Status indicating that the record is current. */
     public static final int STATUS_OK = 0;
@@ -133,7 +137,7 @@
         }
 
         for (int i = 0; i < list1.length; ++i) {
-            if (!list1[i].equals(list2[i + offset])) {
+            if (!MdnsUtils.equalsIgnoreDnsCase(list1[i], list2[i + offset])) {
                 return false;
             }
         }
@@ -201,6 +205,17 @@
     protected abstract void readData(MdnsPacketReader reader) throws IOException;
 
     /**
+     * Write the first fields of the record, which are common fields for questions and answers.
+     *
+     * @param writer The writer to use.
+     */
+    public final void writeHeaderFields(MdnsPacketWriter writer) throws IOException {
+        writer.writeLabels(name);
+        writer.writeUInt16(type);
+        writer.writeUInt16(cls);
+    }
+
+    /**
      * Writes the record to a packet.
      *
      * @param writer The writer to use.
@@ -208,9 +223,7 @@
      */
     @VisibleForTesting
     public final void write(MdnsPacketWriter writer, long now) throws IOException {
-        writer.writeLabels(name);
-        writer.writeUInt16(type);
-        writer.writeUInt16(cls);
+        writeHeaderFields(writer);
 
         writer.writeUInt32(MILLISECONDS.toSeconds(getRemainingTTL(now)));
 
@@ -259,12 +272,13 @@
 
         MdnsRecord otherRecord = (MdnsRecord) other;
 
-        return Arrays.equals(name, otherRecord.name) && (type == otherRecord.type);
+        return MdnsUtils.equalsDnsLabelIgnoreDnsCase(name, otherRecord.name) && (type
+                == otherRecord.type);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(Arrays.hashCode(name), type);
+        return Objects.hash(Arrays.hashCode(MdnsUtils.toDnsLabelsLowerCase(name)), type);
     }
 
     /**
@@ -285,7 +299,7 @@
 
         public Key(int recordType, String[] recordName) {
             this.recordType = recordType;
-            this.recordName = recordName;
+            this.recordName = MdnsUtils.toDnsLabelsLowerCase(recordName);
         }
 
         @Override
@@ -308,4 +322,4 @@
             return (recordType * 31) + Arrays.hashCode(recordName);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
new file mode 100644
index 0000000..1375279
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -0,0 +1,903 @@
+/*
+ * 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 com.android.server.connectivity.mdns;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TargetApi;
+import android.net.LinkAddress;
+import android.net.nsd.NsdServiceInfo;
+import android.os.Build;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.HexDump;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
+
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Random;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A repository of records advertised through {@link MdnsInterfaceAdvertiser}.
+ *
+ * Must be used on a consistent looper thread.
+ */
+@TargetApi(Build.VERSION_CODES.TIRAMISU) // Allow calling T+ APIs; this is only loaded on T+
+public class MdnsRecordRepository {
+    // RFC6762 p.15
+    private static final long MIN_MULTICAST_REPLY_INTERVAL_MS = 1_000L;
+
+    // TTLs as per RFC6762 10.
+    // TTL for records with a host name as the resource record's name (e.g., A, AAAA, HINFO) or a
+    // host name contained within the resource record's rdata (e.g., SRV, reverse mapping PTR
+    // record)
+    private static final long NAME_RECORDS_TTL_MILLIS = TimeUnit.SECONDS.toMillis(120);
+    // TTL for other records
+    private static final long NON_NAME_RECORDS_TTL_MILLIS = TimeUnit.MINUTES.toMillis(75);
+
+    // Top-level domain for link-local queries, as per RFC6762 3.
+    private static final String LOCAL_TLD = "local";
+    // Subtype separator as per RFC6763 7.1 (_printer._sub._http._tcp.local)
+    private static final String SUBTYPE_SEPARATOR = "_sub";
+
+    // Service type for service enumeration (RFC6763 9.)
+    private static final String[] DNS_SD_SERVICE_TYPE =
+            new String[] { "_services", "_dns-sd", "_udp", LOCAL_TLD };
+
+    public static final InetSocketAddress IPV6_ADDR = new InetSocketAddress(
+            MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT);
+    public static final InetSocketAddress IPV4_ADDR = new InetSocketAddress(
+            MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT);
+
+    @NonNull
+    private final Random mDelayGenerator = new Random();
+    // Map of service unique ID -> records for service
+    @NonNull
+    private final SparseArray<ServiceRegistration> mServices = new SparseArray<>();
+    @NonNull
+    private final List<RecordInfo<?>> mGeneralRecords = new ArrayList<>();
+    @NonNull
+    private final Looper mLooper;
+    @NonNull
+    private final String[] mDeviceHostname;
+
+    public MdnsRecordRepository(@NonNull Looper looper, @NonNull String[] deviceHostname) {
+        this(looper, new Dependencies(), deviceHostname);
+    }
+
+    @VisibleForTesting
+    public MdnsRecordRepository(@NonNull Looper looper, @NonNull Dependencies deps,
+            @NonNull String[] deviceHostname) {
+        mDeviceHostname = deviceHostname;
+        mLooper = looper;
+    }
+
+    /**
+     * Dependencies to use with {@link MdnsRecordRepository}, useful for testing.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+
+        /**
+         * @see NetworkInterface#getInetAddresses().
+         */
+        @NonNull
+        public Enumeration<InetAddress> getInterfaceInetAddresses(@NonNull NetworkInterface iface) {
+            return iface.getInetAddresses();
+        }
+    }
+
+    private static class RecordInfo<T extends MdnsRecord> {
+        public final T record;
+        public final NsdServiceInfo serviceInfo;
+
+        /**
+         * Whether the name of this record is expected to be fully owned by the service or may be
+         * advertised by other hosts as well (shared).
+         */
+        public final boolean isSharedName;
+
+        /**
+         * Whether probing is still in progress for the record.
+         */
+        public boolean isProbing;
+
+        /**
+         * Last time (as per SystemClock.elapsedRealtime) when advertised via multicast, 0 if never
+         */
+        public long lastAdvertisedTimeMs;
+
+        /**
+         * Last time (as per SystemClock.elapsedRealtime) when sent via unicast or multicast,
+         * 0 if never
+         */
+        public long lastSentTimeMs;
+
+        RecordInfo(NsdServiceInfo serviceInfo, T record, boolean sharedName,
+                 boolean probing) {
+            this.serviceInfo = serviceInfo;
+            this.record = record;
+            this.isSharedName = sharedName;
+            this.isProbing = probing;
+        }
+    }
+
+    private static class ServiceRegistration {
+        @NonNull
+        public final List<RecordInfo<?>> allRecords;
+        @NonNull
+        public final List<RecordInfo<MdnsPointerRecord>> ptrRecords;
+        @NonNull
+        public final RecordInfo<MdnsServiceRecord> srvRecord;
+        @NonNull
+        public final RecordInfo<MdnsTextRecord> txtRecord;
+        @NonNull
+        public final NsdServiceInfo serviceInfo;
+        @Nullable
+        public final String subtype;
+
+        /**
+         * Whether the service is sending exit announcements and will be destroyed soon.
+         */
+        public boolean exiting = false;
+
+        /**
+         * Create a ServiceRegistration for dns-sd service registration (RFC6763).
+         *
+         * @param deviceHostname Hostname of the device (for the interface used)
+         * @param serviceInfo Service to advertise
+         */
+        ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
+                @Nullable String subtype) {
+            this.serviceInfo = serviceInfo;
+            this.subtype = subtype;
+
+            final String[] serviceType = splitServiceType(serviceInfo);
+            final String[] serviceName = splitFullyQualifiedName(serviceInfo, serviceType);
+
+            // Service PTR record
+            final RecordInfo<MdnsPointerRecord> ptrRecord = new RecordInfo<>(
+                    serviceInfo,
+                    new MdnsPointerRecord(
+                            serviceType,
+                            0L /* receiptTimeMillis */,
+                            false /* cacheFlush */,
+                            NON_NAME_RECORDS_TTL_MILLIS,
+                            serviceName),
+                    true /* sharedName */, true /* probing */);
+
+            if (subtype == null) {
+                this.ptrRecords = Collections.singletonList(ptrRecord);
+            } else {
+                final String[] subtypeName = new String[serviceType.length + 2];
+                System.arraycopy(serviceType, 0, subtypeName, 2, serviceType.length);
+                subtypeName[0] = subtype;
+                subtypeName[1] = SUBTYPE_SEPARATOR;
+                final RecordInfo<MdnsPointerRecord> subtypeRecord = new RecordInfo<>(
+                        serviceInfo,
+                        new MdnsPointerRecord(
+                                subtypeName,
+                                0L /* receiptTimeMillis */,
+                                false /* cacheFlush */,
+                                NON_NAME_RECORDS_TTL_MILLIS,
+                                serviceName),
+                        true /* sharedName */, true /* probing */);
+
+                this.ptrRecords = List.of(ptrRecord, subtypeRecord);
+            }
+
+            srvRecord = new RecordInfo<>(
+                    serviceInfo,
+                    new MdnsServiceRecord(serviceName,
+                            0L /* receiptTimeMillis */,
+                            true /* cacheFlush */,
+                            NAME_RECORDS_TTL_MILLIS, 0 /* servicePriority */, 0 /* serviceWeight */,
+                            serviceInfo.getPort(),
+                            deviceHostname),
+                    false /* sharedName */, true /* probing */);
+
+            txtRecord = new RecordInfo<>(
+                    serviceInfo,
+                    new MdnsTextRecord(serviceName,
+                            0L /* receiptTimeMillis */,
+                            true /* cacheFlush */, // Service name is verified unique after probing
+                            NON_NAME_RECORDS_TTL_MILLIS,
+                            attrsToTextEntries(serviceInfo.getAttributes())),
+                    false /* sharedName */, true /* probing */);
+
+            final ArrayList<RecordInfo<?>> allRecords = new ArrayList<>(5);
+            allRecords.addAll(ptrRecords);
+            allRecords.add(srvRecord);
+            allRecords.add(txtRecord);
+            // Service type enumeration record (RFC6763 9.)
+            allRecords.add(new RecordInfo<>(
+                    serviceInfo,
+                    new MdnsPointerRecord(
+                            DNS_SD_SERVICE_TYPE,
+                            0L /* receiptTimeMillis */,
+                            false /* cacheFlush */,
+                            NON_NAME_RECORDS_TTL_MILLIS,
+                            serviceType),
+                    true /* sharedName */, true /* probing */));
+
+            this.allRecords = Collections.unmodifiableList(allRecords);
+        }
+
+        void setProbing(boolean probing) {
+            for (RecordInfo<?> info : allRecords) {
+                info.isProbing = probing;
+            }
+        }
+    }
+
+    /**
+     * Inform the repository of the latest interface addresses.
+     */
+    public void updateAddresses(@NonNull List<LinkAddress> newAddresses) {
+        mGeneralRecords.clear();
+        for (LinkAddress addr : newAddresses) {
+            final String[] revDnsAddr = getReverseDnsAddress(addr.getAddress());
+            mGeneralRecords.add(new RecordInfo<>(
+                    null /* serviceInfo */,
+                    new MdnsPointerRecord(
+                            revDnsAddr,
+                            0L /* receiptTimeMillis */,
+                            true /* cacheFlush */,
+                            NAME_RECORDS_TTL_MILLIS,
+                            mDeviceHostname),
+                    false /* sharedName */, false /* probing */));
+
+            mGeneralRecords.add(new RecordInfo<>(
+                    null /* serviceInfo */,
+                    new MdnsInetAddressRecord(
+                            mDeviceHostname,
+                            0L /* receiptTimeMillis */,
+                            true /* cacheFlush */,
+                            NAME_RECORDS_TTL_MILLIS,
+                            addr.getAddress()),
+                    false /* sharedName */, false /* probing */));
+        }
+    }
+
+    /**
+     * Add a service to the repository.
+     *
+     * This may remove/replace any existing service that used the name added but is exiting.
+     * @param serviceId A unique service ID.
+     * @param serviceInfo Service info to add.
+     * @return If the added service replaced another with a matching name (which was exiting), the
+     *         ID of the replaced service.
+     * @throws NameConflictException There is already a (non-exiting) service using the name.
+     */
+    public int addService(int serviceId, NsdServiceInfo serviceInfo, @Nullable String subtype)
+            throws NameConflictException {
+        if (mServices.contains(serviceId)) {
+            throw new IllegalArgumentException(
+                    "Service ID must not be reused across registrations: " + serviceId);
+        }
+
+        final int existing = getServiceByName(serviceInfo.getServiceName());
+        // It's OK to re-add a service that is exiting
+        if (existing >= 0 && !mServices.get(existing).exiting) {
+            throw new NameConflictException(existing);
+        }
+
+        final ServiceRegistration registration = new ServiceRegistration(
+                mDeviceHostname, serviceInfo, subtype);
+        mServices.put(serviceId, registration);
+
+        // Remove existing exiting service
+        mServices.remove(existing);
+        return existing;
+    }
+
+    /**
+     * @return The ID of the service identified by its name, or -1 if none.
+     */
+    private int getServiceByName(@NonNull String serviceName) {
+        for (int i = 0; i < mServices.size(); i++) {
+            final ServiceRegistration registration = mServices.valueAt(i);
+            if (MdnsUtils.equalsIgnoreDnsCase(serviceName,
+                    registration.serviceInfo.getServiceName())) {
+                return mServices.keyAt(i);
+            }
+        }
+        return -1;
+    }
+
+    private MdnsProber.ProbingInfo makeProbingInfo(int serviceId,
+            @NonNull MdnsServiceRecord srvRecord) {
+        final List<MdnsRecord> probingRecords = new ArrayList<>();
+        // Probe with cacheFlush cleared; it is set when announcing, as it was verified unique:
+        // RFC6762 10.2
+        probingRecords.add(new MdnsServiceRecord(srvRecord.getName(),
+                0L /* receiptTimeMillis */,
+                false /* cacheFlush */,
+                srvRecord.getTtl(),
+                srvRecord.getServicePriority(), srvRecord.getServiceWeight(),
+                srvRecord.getServicePort(),
+                srvRecord.getServiceHost()));
+
+        return new MdnsProber.ProbingInfo(serviceId, probingRecords);
+    }
+
+    private static List<MdnsServiceInfo.TextEntry> attrsToTextEntries(Map<String, byte[]> attrs) {
+        final List<MdnsServiceInfo.TextEntry> out = new ArrayList<>(attrs.size());
+        for (Map.Entry<String, byte[]> attr : attrs.entrySet()) {
+            out.add(new MdnsServiceInfo.TextEntry(attr.getKey(), attr.getValue()));
+        }
+        return out;
+    }
+
+    /**
+     * Mark a service in the repository as exiting.
+     * @param id ID of the service, used at registration time.
+     * @return The exit announcement to indicate the service was removed, or null if not necessary.
+     */
+    @Nullable
+    public MdnsAnnouncer.ExitAnnouncementInfo exitService(int id) {
+        final ServiceRegistration registration = mServices.get(id);
+        if (registration == null) return null;
+        if (registration.exiting) return null;
+
+        // Send exit (TTL 0) for the PTR records, if at least one was sent (in particular don't send
+        // if still probing)
+        if (CollectionUtils.all(registration.ptrRecords, r -> r.lastSentTimeMs == 0L)) {
+            return null;
+        }
+
+        registration.exiting = true;
+        final List<MdnsRecord> expiredRecords = CollectionUtils.map(registration.ptrRecords,
+                r -> new MdnsPointerRecord(
+                        r.record.getName(),
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        0L /* ttlMillis */,
+                        r.record.getPointer()));
+
+        // Exit should be skipped if the record is still advertised by another service, but that
+        // would be a conflict (2 service registrations with the same service name), so it would
+        // not have been allowed by the repository.
+        return new MdnsAnnouncer.ExitAnnouncementInfo(id, expiredRecords);
+    }
+
+    public void removeService(int id) {
+        mServices.remove(id);
+    }
+
+    /**
+     * @return The number of services currently held in the repository, including exiting services.
+     */
+    public int getServicesCount() {
+        return mServices.size();
+    }
+
+    /**
+     * Remove all services from the repository
+     * @return IDs of the removed services
+     */
+    @NonNull
+    public int[] clearServices() {
+        final int[] ret = new int[mServices.size()];
+        for (int i = 0; i < mServices.size(); i++) {
+            ret[i] = mServices.keyAt(i);
+        }
+        mServices.clear();
+        return ret;
+    }
+
+    /**
+     * Info about a reply to be sent.
+     */
+    public static class ReplyInfo {
+        @NonNull
+        public final List<MdnsRecord> answers;
+        @NonNull
+        public final List<MdnsRecord> additionalAnswers;
+        public final long sendDelayMs;
+        @NonNull
+        public final InetSocketAddress destination;
+
+        public ReplyInfo(
+                @NonNull List<MdnsRecord> answers,
+                @NonNull List<MdnsRecord> additionalAnswers,
+                long sendDelayMs,
+                @NonNull InetSocketAddress destination) {
+            this.answers = answers;
+            this.additionalAnswers = additionalAnswers;
+            this.sendDelayMs = sendDelayMs;
+            this.destination = destination;
+        }
+
+        @Override
+        public String toString() {
+            return "{ReplyInfo to " + destination + ", answers: " + answers.size()
+                    + ", additionalAnswers: " + additionalAnswers.size()
+                    + ", sendDelayMs " + sendDelayMs + "}";
+        }
+    }
+
+    /**
+     * Get the reply to send to an incoming packet.
+     *
+     * @param packet The incoming packet.
+     * @param src The source address of the incoming packet.
+     */
+    @Nullable
+    public ReplyInfo getReply(MdnsPacket packet, InetSocketAddress src) {
+        final long now = SystemClock.elapsedRealtime();
+        final boolean replyUnicast = (packet.flags & MdnsConstants.QCLASS_UNICAST) != 0;
+        final ArrayList<MdnsRecord> additionalAnswerRecords = new ArrayList<>();
+        final ArrayList<RecordInfo<?>> answerInfo = new ArrayList<>();
+        for (MdnsRecord question : packet.questions) {
+            // Add answers from general records
+            addReplyFromService(question, mGeneralRecords, null /* servicePtrRecord */,
+                    null /* serviceSrvRecord */, null /* serviceTxtRecord */, replyUnicast, now,
+                    answerInfo, additionalAnswerRecords);
+
+            // Add answers from each service
+            for (int i = 0; i < mServices.size(); i++) {
+                final ServiceRegistration registration = mServices.valueAt(i);
+                if (registration.exiting) continue;
+                addReplyFromService(question, registration.allRecords, registration.ptrRecords,
+                        registration.srvRecord, registration.txtRecord, replyUnicast, now,
+                        answerInfo, additionalAnswerRecords);
+            }
+        }
+
+        if (answerInfo.size() == 0 && additionalAnswerRecords.size() == 0) {
+            return null;
+        }
+
+        // Determine the send delay
+        final long delayMs;
+        if ((packet.flags & MdnsConstants.FLAG_TRUNCATED) != 0) {
+            // RFC 6762 6.: 400-500ms delay if TC bit is set
+            delayMs = 400L + mDelayGenerator.nextInt(100);
+        } else if (packet.questions.size() > 1
+                || CollectionUtils.any(answerInfo, a -> a.isSharedName)) {
+            // 20-120ms if there may be responses from other hosts (not a fully owned
+            // name) (RFC 6762 6.), or if there are multiple questions (6.3).
+            // TODO: this should be 0 if this is a probe query ("can be distinguished from a
+            // normal query by the fact that a probe query contains a proposed record in the
+            // Authority Section that answers the question" in 6.), and the reply is for a fully
+            // owned record.
+            delayMs = 20L + mDelayGenerator.nextInt(100);
+        } else {
+            delayMs = 0L;
+        }
+
+        // Determine the send destination
+        final InetSocketAddress dest;
+        if (replyUnicast) {
+            dest = src;
+        } else if (src.getAddress() instanceof Inet4Address) {
+            dest = IPV4_ADDR;
+        } else {
+            dest = IPV6_ADDR;
+        }
+
+        // Build the list of answer records from their RecordInfo
+        final ArrayList<MdnsRecord> answerRecords = new ArrayList<>(answerInfo.size());
+        for (RecordInfo<?> info : answerInfo) {
+            // TODO: consider actual packet send delay after response aggregation
+            info.lastSentTimeMs = now + delayMs;
+            if (!replyUnicast) {
+                info.lastAdvertisedTimeMs = info.lastSentTimeMs;
+            }
+            answerRecords.add(info.record);
+        }
+
+        return new ReplyInfo(answerRecords, additionalAnswerRecords, delayMs, dest);
+    }
+
+    /**
+     * Add answers and additional answers for a question, from a ServiceRegistration.
+     */
+    private void addReplyFromService(@NonNull MdnsRecord question,
+            @NonNull List<RecordInfo<?>> serviceRecords,
+            @Nullable List<RecordInfo<MdnsPointerRecord>> servicePtrRecords,
+            @Nullable RecordInfo<MdnsServiceRecord> serviceSrvRecord,
+            @Nullable RecordInfo<MdnsTextRecord> serviceTxtRecord,
+            boolean replyUnicast, long now, @NonNull List<RecordInfo<?>> answerInfo,
+            @NonNull List<MdnsRecord> additionalAnswerRecords) {
+        boolean hasDnsSdPtrRecordAnswer = false;
+        boolean hasDnsSdSrvRecordAnswer = false;
+        boolean hasFullyOwnedNameMatch = false;
+        boolean hasKnownAnswer = false;
+
+        final int answersStartIndex = answerInfo.size();
+        for (RecordInfo<?> info : serviceRecords) {
+            if (info.isProbing) continue;
+
+             /* RFC6762 6.: the record name must match the question name, the record rrtype
+             must match the question qtype unless the qtype is "ANY" (255) or the rrtype is
+             "CNAME" (5), and the record rrclass must match the question qclass unless the
+             qclass is "ANY" (255) */
+            if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(info.record.getName(), question.getName())) {
+                continue;
+            }
+            hasFullyOwnedNameMatch |= !info.isSharedName;
+
+            // The repository does not store CNAME records
+            if (question.getType() != MdnsRecord.TYPE_ANY
+                    && question.getType() != info.record.getType()) {
+                continue;
+            }
+            if (question.getRecordClass() != MdnsRecord.CLASS_ANY
+                    && question.getRecordClass() != info.record.getRecordClass()) {
+                continue;
+            }
+
+            hasKnownAnswer = true;
+            hasDnsSdPtrRecordAnswer |= (servicePtrRecords != null
+                    && CollectionUtils.any(servicePtrRecords, r -> info == r));
+            hasDnsSdSrvRecordAnswer |= (info == serviceSrvRecord);
+
+            // TODO: responses to probe queries should bypass this check and only ensure the
+            // reply is sent 250ms after the last sent time (RFC 6762 p.15)
+            if (!replyUnicast && info.lastAdvertisedTimeMs > 0L
+                    && now - info.lastAdvertisedTimeMs < MIN_MULTICAST_REPLY_INTERVAL_MS) {
+                continue;
+            }
+
+            // TODO: Don't reply if in known answers of the querier (7.1) if TTL is > half
+
+            answerInfo.add(info);
+        }
+
+        // RFC6762 6.1:
+        // "Any time a responder receives a query for a name for which it has verified exclusive
+        // ownership, for a type for which that name has no records, the responder MUST [...]
+        // respond asserting the nonexistence of that record"
+        if (hasFullyOwnedNameMatch && !hasKnownAnswer) {
+            additionalAnswerRecords.add(new MdnsNsecRecord(
+                    question.getName(),
+                    0L /* receiptTimeMillis */,
+                    true /* cacheFlush */,
+                    // TODO: RFC6762 6.1: "In general, the TTL given for an NSEC record SHOULD
+                    // be the same as the TTL that the record would have had, had it existed."
+                    NAME_RECORDS_TTL_MILLIS,
+                    question.getName(),
+                    new int[] { question.getType() }));
+        }
+
+        // No more records to add if no answer
+        if (answerInfo.size() == answersStartIndex) return;
+
+        final List<RecordInfo<?>> additionalAnswerInfo = new ArrayList<>();
+        // RFC6763 12.1: if including PTR record, include the SRV and TXT records it names
+        if (hasDnsSdPtrRecordAnswer) {
+            if (serviceTxtRecord != null) {
+                additionalAnswerInfo.add(serviceTxtRecord);
+            }
+            if (serviceSrvRecord != null) {
+                additionalAnswerInfo.add(serviceSrvRecord);
+            }
+        }
+
+        // RFC6763 12.1&.2: if including PTR or SRV record, include the address records it names
+        if (hasDnsSdPtrRecordAnswer || hasDnsSdSrvRecordAnswer) {
+            for (RecordInfo<?> record : mGeneralRecords) {
+                if (record.record instanceof MdnsInetAddressRecord) {
+                    additionalAnswerInfo.add(record);
+                }
+            }
+        }
+
+        for (RecordInfo<?> info : additionalAnswerInfo) {
+            additionalAnswerRecords.add(info.record);
+        }
+
+        // RFC6762 6.1: negative responses
+        addNsecRecordsForUniqueNames(additionalAnswerRecords,
+                answerInfo.listIterator(answersStartIndex),
+                additionalAnswerInfo.listIterator());
+    }
+
+    /**
+     * Add NSEC records indicating that the response records are unique.
+     *
+     * Following RFC6762 6.1:
+     * "On receipt of a question for a particular name, rrtype, and rrclass, for which a responder
+     * does have one or more unique answers, the responder MAY also include an NSEC record in the
+     * Additional Record Section indicating the nonexistence of other rrtypes for that name and
+     * rrclass."
+     * @param destinationList List to add the NSEC records to.
+     * @param answerRecords Lists of answered records based on which to add NSEC records (typically
+     *                      answer and additionalAnswer sections)
+     */
+    @SafeVarargs
+    private static void addNsecRecordsForUniqueNames(
+            List<MdnsRecord> destinationList,
+            Iterator<RecordInfo<?>>... answerRecords) {
+        // Group unique records by name. Use a TreeMap with comparator as arrays don't implement
+        // equals / hashCode.
+        final Map<String[], List<MdnsRecord>> nsecByName = new TreeMap<>(Arrays::compare);
+        // But keep the list of names in added order, otherwise records would be sorted in
+        // alphabetical order instead of the order of the original records, which would look like
+        // inconsistent behavior depending on service name.
+        final List<String[]> namesInAddedOrder = new ArrayList<>();
+        for (Iterator<RecordInfo<?>> answers : answerRecords) {
+            addNonSharedRecordsToMap(answers, nsecByName, namesInAddedOrder);
+        }
+
+        for (String[] nsecName : namesInAddedOrder) {
+            final List<MdnsRecord> entryRecords = nsecByName.get(nsecName);
+            long minTtl = Long.MAX_VALUE;
+            final Set<Integer> types = new ArraySet<>(entryRecords.size());
+            for (MdnsRecord record : entryRecords) {
+                if (minTtl > record.getTtl()) minTtl = record.getTtl();
+                types.add(record.getType());
+            }
+
+            destinationList.add(new MdnsNsecRecord(
+                    nsecName,
+                    0L /* receiptTimeMillis */,
+                    true /* cacheFlush */,
+                    minTtl,
+                    nsecName,
+                    CollectionUtils.toIntArray(types)));
+        }
+    }
+
+    /**
+     * Add non-shared records to a map listing them by record name, and to a list of names that
+     * remembers the adding order.
+     *
+     * In the destination map records are grouped by name; so the map has one key per record name,
+     * and the values are the lists of different records that share the same name.
+     * @param records Records to scan.
+     * @param dest Map to add the records to.
+     * @param namesInAddedOrder List of names to add the names in order, keeping the first
+     *                          occurrence of each name.
+     */
+    private static void addNonSharedRecordsToMap(
+            Iterator<RecordInfo<?>> records,
+            Map<String[], List<MdnsRecord>> dest,
+            List<String[]> namesInAddedOrder) {
+        while (records.hasNext()) {
+            final RecordInfo<?> record = records.next();
+            if (record.isSharedName) continue;
+            final List<MdnsRecord> recordsForName = dest.computeIfAbsent(record.record.name,
+                    key -> {
+                        namesInAddedOrder.add(key);
+                        return new ArrayList<>();
+                    });
+            recordsForName.add(record.record);
+        }
+    }
+
+    /**
+     * Called to indicate that probing succeeded for a service.
+     * @param probeSuccessInfo The successful probing info.
+     * @return The {@link MdnsAnnouncer.AnnouncementInfo} to send, now that probing has succeeded.
+     */
+    public MdnsAnnouncer.AnnouncementInfo onProbingSucceeded(
+            MdnsProber.ProbingInfo probeSuccessInfo)
+            throws IOException {
+
+        final ServiceRegistration registration = mServices.get(probeSuccessInfo.getServiceId());
+        if (registration == null) throw new IOException(
+                "Service is not registered: " + probeSuccessInfo.getServiceId());
+        registration.setProbing(false);
+
+        final ArrayList<MdnsRecord> answers = new ArrayList<>();
+        final ArrayList<MdnsRecord> additionalAnswers = new ArrayList<>();
+
+        // Interface address records in general records
+        for (RecordInfo<?> record : mGeneralRecords) {
+            answers.add(record.record);
+        }
+
+        // All service records
+        for (RecordInfo<?> info : registration.allRecords) {
+            answers.add(info.record);
+        }
+
+        addNsecRecordsForUniqueNames(additionalAnswers,
+                mGeneralRecords.iterator(), registration.allRecords.iterator());
+
+        return new MdnsAnnouncer.AnnouncementInfo(probeSuccessInfo.getServiceId(),
+                answers, additionalAnswers);
+    }
+
+    /**
+     * Get the service IDs of services conflicting with a received packet.
+     */
+    public Set<Integer> getConflictingServices(MdnsPacket packet) {
+        // Avoid allocating a new set for each incoming packet: use an empty set by default.
+        Set<Integer> conflicting = Collections.emptySet();
+        for (MdnsRecord record : packet.answers) {
+            for (int i = 0; i < mServices.size(); i++) {
+                final ServiceRegistration registration = mServices.valueAt(i);
+                if (registration.exiting) continue;
+
+                // Only look for conflicts in service name, as a different service name can be used
+                // if there is a conflict, but there is nothing actionable if any other conflict
+                // happens. In fact probing is only done for the service name in the SRV record.
+                // This means only SRV and TXT records need to be checked.
+                final RecordInfo<MdnsServiceRecord> srvRecord = registration.srvRecord;
+                if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(record.getName(),
+                        srvRecord.record.getName())) {
+                    continue;
+                }
+
+                // As per RFC6762 9., it's fine if the "conflict" is an identical record with same
+                // data.
+                if (record instanceof MdnsServiceRecord) {
+                    final MdnsServiceRecord local = srvRecord.record;
+                    final MdnsServiceRecord other = (MdnsServiceRecord) record;
+                    // Note "equals" does not consider TTL or receipt time, as intended here
+                    if (Objects.equals(local, other)) {
+                        continue;
+                    }
+                }
+
+                if (record instanceof MdnsTextRecord) {
+                    final MdnsTextRecord local = registration.txtRecord.record;
+                    final MdnsTextRecord other = (MdnsTextRecord) record;
+                    if (Objects.equals(local, other)) {
+                        continue;
+                    }
+                }
+
+                if (conflicting.size() == 0) {
+                    // Conflict was found: use a mutable set
+                    conflicting = new ArraySet<>();
+                }
+                final int serviceId = mServices.keyAt(i);
+                conflicting.add(serviceId);
+            }
+        }
+
+        return conflicting;
+    }
+
+    /**
+     * (Re)set a service to the probing state.
+     * @return The {@link MdnsProber.ProbingInfo} to send for probing.
+     */
+    @Nullable
+    public MdnsProber.ProbingInfo setServiceProbing(int serviceId) {
+        final ServiceRegistration registration = mServices.get(serviceId);
+        if (registration == null) return null;
+
+        registration.setProbing(true);
+        return makeProbingInfo(serviceId, registration.srvRecord.record);
+    }
+
+    /**
+     * Indicates whether a given service is in probing state.
+     */
+    public boolean isProbing(int serviceId) {
+        final ServiceRegistration registration = mServices.get(serviceId);
+        if (registration == null) return false;
+
+        return registration.srvRecord.isProbing;
+    }
+
+    /**
+     * Return whether the repository has an active (non-exiting) service for the given ID.
+     */
+    public boolean hasActiveService(int serviceId) {
+        final ServiceRegistration registration = mServices.get(serviceId);
+        if (registration == null) return false;
+
+        return !registration.exiting;
+    }
+
+    /**
+     * Rename a service to the newly provided info, following a conflict.
+     *
+     * If the specified service does not exist, this returns null.
+     */
+    @Nullable
+    public MdnsProber.ProbingInfo renameServiceForConflict(int serviceId, NsdServiceInfo newInfo) {
+        final ServiceRegistration existing = mServices.get(serviceId);
+        if (existing == null) return null;
+
+        final ServiceRegistration newService = new ServiceRegistration(
+                mDeviceHostname, newInfo, existing.subtype);
+        mServices.put(serviceId, newService);
+        return makeProbingInfo(serviceId, newService.srvRecord.record);
+    }
+
+    /**
+     * Called when {@link MdnsAdvertiser} sent an advertisement for the given service.
+     */
+    public void onAdvertisementSent(int serviceId) {
+        final ServiceRegistration registration = mServices.get(serviceId);
+        if (registration == null) return;
+
+        final long now = SystemClock.elapsedRealtime();
+        for (RecordInfo<?> record : registration.allRecords) {
+            record.lastSentTimeMs = now;
+            record.lastAdvertisedTimeMs = now;
+        }
+    }
+
+    /**
+     * Compute:
+     * 2001:db8::1 --> 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.B.D.0.1.0.0.2.ip6.arpa
+     *
+     * Or:
+     * 192.0.2.123 --> 123.2.0.192.in-addr.arpa
+     */
+    @VisibleForTesting
+    public static String[] getReverseDnsAddress(@NonNull InetAddress addr) {
+        // xxx.xxx.xxx.xxx.in-addr.arpa (up to 28 characters)
+        // or 32 hex characters separated by dots + .ip6.arpa
+        final byte[] addrBytes = addr.getAddress();
+        final List<String> out = new ArrayList<>();
+        if (addr instanceof Inet4Address) {
+            for (int i = addrBytes.length - 1; i >= 0; i--) {
+                out.add(String.valueOf(Byte.toUnsignedInt(addrBytes[i])));
+            }
+            out.add("in-addr");
+        } else {
+            final String hexAddr = HexDump.toHexString(addrBytes);
+
+            for (int i = hexAddr.length() - 1; i >= 0; i--) {
+                out.add(String.valueOf(hexAddr.charAt(i)));
+            }
+            out.add("ip6");
+        }
+        out.add("arpa");
+
+        return out.toArray(new String[0]);
+    }
+
+    private static String[] splitFullyQualifiedName(
+            @NonNull NsdServiceInfo info, @NonNull String[] serviceType) {
+        final String[] split = new String[serviceType.length + 1];
+        split[0] = info.getServiceName();
+        System.arraycopy(serviceType, 0, split, 1, serviceType.length);
+
+        return split;
+    }
+
+    private static String[] splitServiceType(@NonNull NsdServiceInfo info) {
+        // String.split(pattern, 0) removes trailing empty strings, which would appear when
+        // splitting "domain.name." (with a dot a the end), so this is what is needed here.
+        final String[] split = info.getServiceType().split("\\.", 0);
+        final String[] type = new String[split.length + 1];
+        System.arraycopy(split, 0, type, 0, split.length);
+        type[split.length] = LOCAL_TLD;
+
+        return type;
+    }
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
new file mode 100644
index 0000000..8bc598d
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
@@ -0,0 +1,153 @@
+/*
+ * 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 com.android.server.connectivity.mdns;
+
+import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.server.connectivity.mdns.MdnsRecordRepository.ReplyInfo;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetSocketAddress;
+import java.net.MulticastSocket;
+import java.util.Collections;
+
+/**
+ * A class that handles sending mDNS replies to a {@link MulticastSocket}, possibly queueing them
+ * to be sent after some delay.
+ *
+ * TODO: implement sending after a delay, combining queued replies and duplicate answer suppression
+ */
+public class MdnsReplySender {
+    private static final boolean DBG = MdnsAdvertiser.DBG;
+    private static final int MSG_SEND = 1;
+
+    private final String mLogTag;
+    @NonNull
+    private final MdnsInterfaceSocket mSocket;
+    @NonNull
+    private final Handler mHandler;
+    @NonNull
+    private final byte[] mPacketCreationBuffer;
+
+    public MdnsReplySender(@NonNull String interfaceTag, @NonNull Looper looper,
+            @NonNull MdnsInterfaceSocket socket, @NonNull byte[] packetCreationBuffer) {
+        mHandler = new SendHandler(looper);
+        mLogTag = MdnsReplySender.class.getSimpleName() + "/" +  interfaceTag;
+        mSocket = socket;
+        mPacketCreationBuffer = packetCreationBuffer;
+    }
+
+    /**
+     * Queue a reply to be sent when its send delay expires.
+     */
+    public void queueReply(@NonNull ReplyInfo reply) {
+        ensureRunningOnHandlerThread(mHandler);
+        // TODO: implement response aggregation (RFC 6762 6.4)
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SEND, reply), reply.sendDelayMs);
+
+        if (DBG) {
+            Log.v(mLogTag, "Scheduling " + reply);
+        }
+    }
+
+    /**
+     * Send a packet immediately.
+     *
+     * Must be called on the looper thread used by the {@link MdnsReplySender}.
+     */
+    public void sendNow(@NonNull MdnsPacket packet, @NonNull InetSocketAddress destination)
+            throws IOException {
+        ensureRunningOnHandlerThread(mHandler);
+        if (!((destination.getAddress() instanceof Inet6Address && mSocket.hasJoinedIpv6())
+                || (destination.getAddress() instanceof Inet4Address && mSocket.hasJoinedIpv4()))) {
+            // Skip sending if the socket has not joined the v4/v6 group (there was no address)
+            return;
+        }
+
+        // TODO: support packets over size (send in multiple packets with TC bit set)
+        final MdnsPacketWriter writer = new MdnsPacketWriter(mPacketCreationBuffer);
+
+        writer.writeUInt16(0); // Transaction ID (advertisement: 0)
+        writer.writeUInt16(packet.flags); // Response, authoritative (rfc6762 18.4)
+        writer.writeUInt16(packet.questions.size()); // questions count
+        writer.writeUInt16(packet.answers.size()); // answers count
+        writer.writeUInt16(packet.authorityRecords.size()); // authority entries count
+        writer.writeUInt16(packet.additionalRecords.size()); // additional records count
+
+        for (MdnsRecord record : packet.questions) {
+            // Questions do not have TTL or data
+            record.writeHeaderFields(writer);
+        }
+        for (MdnsRecord record : packet.answers) {
+            record.write(writer, 0L);
+        }
+        for (MdnsRecord record : packet.authorityRecords) {
+            record.write(writer, 0L);
+        }
+        for (MdnsRecord record : packet.additionalRecords) {
+            record.write(writer, 0L);
+        }
+
+        final int len = writer.getWritePosition();
+        final byte[] outBuffer = new byte[len];
+        System.arraycopy(mPacketCreationBuffer, 0, outBuffer, 0, len);
+
+        mSocket.send(new DatagramPacket(outBuffer, 0, len, destination));
+    }
+
+    /**
+     * Cancel all pending sends.
+     */
+    public void cancelAll() {
+        ensureRunningOnHandlerThread(mHandler);
+        mHandler.removeMessages(MSG_SEND);
+    }
+
+    private class SendHandler extends Handler {
+        SendHandler(@NonNull Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(@NonNull Message msg) {
+            final ReplyInfo replyInfo = (ReplyInfo) msg.obj;
+            if (DBG) Log.v(mLogTag, "Sending " + replyInfo);
+
+            final int flags = 0x8400; // Response, authoritative (rfc6762 18.4)
+            final MdnsPacket packet = new MdnsPacket(flags,
+                    Collections.emptyList() /* questions */,
+                    replyInfo.answers,
+                    Collections.emptyList() /* authorityRecords */,
+                    replyInfo.additionalAnswers);
+
+            try {
+                sendNow(packet, replyInfo.destination);
+            } catch (IOException e) {
+                Log.e(mLogTag, "Error sending MDNS response", e);
+            }
+        }
+    }
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
new file mode 100644
index 0000000..28aa640
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Network;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+/** An mDNS response. */
+public class MdnsResponse {
+    private final List<MdnsRecord> records;
+    private final List<MdnsPointerRecord> pointerRecords;
+    private MdnsServiceRecord serviceRecord;
+    private MdnsTextRecord textRecord;
+    @NonNull private List<MdnsInetAddressRecord> inet4AddressRecords;
+    @NonNull private List<MdnsInetAddressRecord> inet6AddressRecords;
+    private long lastUpdateTime;
+    private final int interfaceIndex;
+    @Nullable private final Network network;
+    @NonNull private final String[] serviceName;
+
+    /** Constructs a new, empty response. */
+    public MdnsResponse(long now, @NonNull String[] serviceName, int interfaceIndex,
+            @Nullable Network network) {
+        lastUpdateTime = now;
+        records = new LinkedList<>();
+        pointerRecords = new LinkedList<>();
+        inet4AddressRecords = new ArrayList<>();
+        inet6AddressRecords = new ArrayList<>();
+        this.interfaceIndex = interfaceIndex;
+        this.network = network;
+        this.serviceName = serviceName;
+    }
+
+    public MdnsResponse(@NonNull MdnsResponse base) {
+        records = new ArrayList<>(base.records);
+        pointerRecords = new ArrayList<>(base.pointerRecords);
+        serviceRecord = base.serviceRecord;
+        textRecord = base.textRecord;
+        inet4AddressRecords = new ArrayList<>(base.inet4AddressRecords);
+        inet6AddressRecords = new ArrayList<>(base.inet6AddressRecords);
+        lastUpdateTime = base.lastUpdateTime;
+        serviceName = base.serviceName;
+        interfaceIndex = base.interfaceIndex;
+        network = base.network;
+    }
+
+    /**
+     * Compare records for equality, including their TTL.
+     *
+     * MdnsRecord#equals ignores TTL and receiptTimeMillis, but methods in this class need to update
+     * records when the TTL changes (especially for goodbye announcements).
+     */
+    private boolean recordsAreSame(MdnsRecord a, MdnsRecord b) {
+        if (!Objects.equals(a, b)) return false;
+        return a == null || a.getTtl() == b.getTtl();
+    }
+
+    private <T extends MdnsRecord> boolean addOrReplaceRecord(@NonNull T record,
+            @NonNull List<T> recordsList) {
+        final int existing = recordsList.indexOf(record);
+        if (existing >= 0) {
+            if (recordsAreSame(record, recordsList.get(existing))) {
+                return false;
+            }
+            final MdnsRecord existedRecord = recordsList.remove(existing);
+            records.remove(existedRecord);
+        }
+        recordsList.add(record);
+        records.add(record);
+        return true;
+    }
+
+    /**
+     * Adds a pointer record.
+     *
+     * @return <code>true</code> if the record was added, or <code>false</code> if a matching
+     * pointer record is already present in the response with the same TTL.
+     */
+    public synchronized boolean addPointerRecord(MdnsPointerRecord pointerRecord) {
+        if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(serviceName, pointerRecord.getPointer())) {
+            throw new IllegalArgumentException(
+                    "Pointer records for different service names cannot be added");
+        }
+        return addOrReplaceRecord(pointerRecord, pointerRecords);
+    }
+
+    /** Gets the pointer records. */
+    public synchronized List<MdnsPointerRecord> getPointerRecords() {
+        // Returns a shallow copy.
+        return new LinkedList<>(pointerRecords);
+    }
+
+    public synchronized boolean hasPointerRecords() {
+        return !pointerRecords.isEmpty();
+    }
+
+    @VisibleForTesting
+    synchronized void clearPointerRecords() {
+        pointerRecords.clear();
+    }
+
+    public synchronized boolean hasSubtypes() {
+        for (MdnsPointerRecord pointerRecord : pointerRecords) {
+            if (pointerRecord.hasSubtype()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Nullable
+    public synchronized List<String> getSubtypes() {
+        List<String> subtypes = null;
+        for (MdnsPointerRecord pointerRecord : pointerRecords) {
+            String pointerRecordSubtype = pointerRecord.getSubtype();
+            if (pointerRecordSubtype != null) {
+                if (subtypes == null) {
+                    subtypes = new LinkedList<>();
+                }
+                subtypes.add(pointerRecordSubtype);
+            }
+        }
+
+        return subtypes;
+    }
+
+    @VisibleForTesting
+    public synchronized void removeSubtypes() {
+        Iterator<MdnsPointerRecord> iter = pointerRecords.iterator();
+        while (iter.hasNext()) {
+            MdnsPointerRecord pointerRecord = iter.next();
+            if (pointerRecord.hasSubtype()) {
+                iter.remove();
+            }
+        }
+    }
+
+    /** Sets the service record. */
+    public synchronized boolean setServiceRecord(MdnsServiceRecord serviceRecord) {
+        if (recordsAreSame(this.serviceRecord, serviceRecord)) {
+            return false;
+        }
+        if (this.serviceRecord != null) {
+            records.remove(this.serviceRecord);
+        }
+        this.serviceRecord = serviceRecord;
+        if (this.serviceRecord != null) {
+            records.add(this.serviceRecord);
+        }
+        return true;
+    }
+
+    /** Gets the service record. */
+    public synchronized MdnsServiceRecord getServiceRecord() {
+        return serviceRecord;
+    }
+
+    public synchronized boolean hasServiceRecord() {
+        return serviceRecord != null;
+    }
+
+    /** Sets the text record. */
+    public synchronized boolean setTextRecord(MdnsTextRecord textRecord) {
+        if (recordsAreSame(this.textRecord, textRecord)) {
+            return false;
+        }
+        if (this.textRecord != null) {
+            records.remove(this.textRecord);
+        }
+        this.textRecord = textRecord;
+        if (this.textRecord != null) {
+            records.add(this.textRecord);
+        }
+        return true;
+    }
+
+    /** Gets the text record. */
+    public synchronized MdnsTextRecord getTextRecord() {
+        return textRecord;
+    }
+
+    public synchronized boolean hasTextRecord() {
+        return textRecord != null;
+    }
+
+    /** Add the IPv4 address record. */
+    public synchronized boolean addInet4AddressRecord(
+            @NonNull MdnsInetAddressRecord newInet4AddressRecord) {
+        return addOrReplaceRecord(newInet4AddressRecord, inet4AddressRecords);
+    }
+
+    /** Gets the IPv4 address records. */
+    @NonNull
+    public synchronized List<MdnsInetAddressRecord> getInet4AddressRecords() {
+        return Collections.unmodifiableList(inet4AddressRecords);
+    }
+
+    /** Return the first IPv4 address record or null if no record. */
+    @Nullable
+    public synchronized MdnsInetAddressRecord getInet4AddressRecord() {
+        return inet4AddressRecords.isEmpty() ? null : inet4AddressRecords.get(0);
+    }
+
+    /** Check whether response has IPv4 address record */
+    public synchronized boolean hasInet4AddressRecord() {
+        return !inet4AddressRecords.isEmpty();
+    }
+
+    /** Clear all IPv4 address records */
+    synchronized void clearInet4AddressRecords() {
+        for (MdnsInetAddressRecord record : inet4AddressRecords) {
+            records.remove(record);
+        }
+        inet4AddressRecords.clear();
+    }
+
+    /** Sets the IPv6 address records. */
+    public synchronized boolean addInet6AddressRecord(
+            @NonNull MdnsInetAddressRecord newInet6AddressRecord) {
+        return addOrReplaceRecord(newInet6AddressRecord, inet6AddressRecords);
+    }
+
+    /**
+     * Returns the index of the network interface at which this response was received. Can be set to
+     * {@link MdnsSocket#INTERFACE_INDEX_UNSPECIFIED} if unset.
+     */
+    public int getInterfaceIndex() {
+        return interfaceIndex;
+    }
+
+    /**
+     * Returns the network at which this response was received, or null if the network is unknown.
+     */
+    @Nullable
+    public Network getNetwork() {
+        return network;
+    }
+
+    /** Gets all IPv6 address records. */
+    public synchronized List<MdnsInetAddressRecord> getInet6AddressRecords() {
+        return Collections.unmodifiableList(inet6AddressRecords);
+    }
+
+    /** Return the first IPv6 address record or null if no record. */
+    @Nullable
+    public synchronized MdnsInetAddressRecord getInet6AddressRecord() {
+        return inet6AddressRecords.isEmpty() ? null : inet6AddressRecords.get(0);
+    }
+
+    /** Check whether response has IPv6 address record */
+    public synchronized boolean hasInet6AddressRecord() {
+        return !inet6AddressRecords.isEmpty();
+    }
+
+    /** Clear all IPv6 address records */
+    synchronized void clearInet6AddressRecords() {
+        for (MdnsInetAddressRecord record : inet6AddressRecords) {
+            records.remove(record);
+        }
+        inet6AddressRecords.clear();
+    }
+
+    /** Gets all of the records. */
+    public synchronized List<MdnsRecord> getRecords() {
+        return new LinkedList<>(records);
+    }
+
+    /**
+     * Drop address records if they are for a hostname that does not match the service record.
+     *
+     * @return True if the records were dropped.
+     */
+    public synchronized boolean dropUnmatchedAddressRecords() {
+        if (this.serviceRecord == null) return false;
+        boolean dropAddressRecords = false;
+
+        for (MdnsInetAddressRecord inetAddressRecord : getInet4AddressRecords()) {
+            if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(
+                    this.serviceRecord.getServiceHost(), inetAddressRecord.getName())) {
+                dropAddressRecords = true;
+            }
+        }
+        for (MdnsInetAddressRecord inetAddressRecord : getInet6AddressRecords()) {
+            if (!MdnsUtils.equalsDnsLabelIgnoreDnsCase(
+                    this.serviceRecord.getServiceHost(), inetAddressRecord.getName())) {
+                dropAddressRecords = true;
+            }
+        }
+
+        if (dropAddressRecords) {
+            clearInet4AddressRecords();
+            clearInet6AddressRecords();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Tests if the response is complete. A response is considered complete if it contains SRV,
+     * TXT, and A (for IPv4) or AAAA (for IPv6) records. The service type->name mapping is always
+     * known when constructing a MdnsResponse, so this may return true when there is no PTR record.
+     */
+    public synchronized boolean isComplete() {
+        return (serviceRecord != null)
+                && (textRecord != null)
+                && (!inet4AddressRecords.isEmpty() || !inet6AddressRecords.isEmpty());
+    }
+
+    /**
+     * Returns the key for this response. The key uniquely identifies the response by its service
+     * name.
+     */
+    @Nullable
+    public String getServiceInstanceName() {
+        return serviceName.length > 0 ? serviceName[0] : null;
+    }
+
+    @NonNull
+    public String[] getServiceName() {
+        return serviceName;
+    }
+
+    /**
+     * Tests if this response is a goodbye message. This will be true if a service record is present
+     * and any of the records have a TTL of 0.
+     */
+    public synchronized boolean isGoodbye() {
+        if (getServiceInstanceName() != null) {
+            for (MdnsRecord record : records) {
+                // Expiring PTR records with subtypes just signal a change in known supported
+                // criteria, not the device itself going offline, so ignore those.
+                if ((record instanceof MdnsPointerRecord)
+                        && ((MdnsPointerRecord) record).hasSubtype()) {
+                    continue;
+                }
+
+                if (record.getTtl() == 0) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Writes the response to a packet.
+     *
+     * @param writer The writer to use.
+     * @param now    The current time. This is used to write updated TTLs that reflect the remaining
+     *               TTL
+     *               since the response was received.
+     * @return The number of records that were written.
+     * @throws IOException If an error occurred while writing (typically indicating overflow).
+     */
+    public synchronized int write(MdnsPacketWriter writer, long now) throws IOException {
+        int count = 0;
+        for (MdnsPointerRecord pointerRecord : pointerRecords) {
+            pointerRecord.write(writer, now);
+            ++count;
+        }
+
+        if (serviceRecord != null) {
+            serviceRecord.write(writer, now);
+            ++count;
+        }
+
+        if (textRecord != null) {
+            textRecord.write(writer, now);
+            ++count;
+        }
+
+        for (MdnsInetAddressRecord inetAddressRecord : inet4AddressRecords) {
+            inetAddressRecord.write(writer, now);
+            ++count;
+        }
+
+        for (MdnsInetAddressRecord inetAddressRecord : inet6AddressRecords) {
+            inetAddressRecord.write(writer, now);
+            ++count;
+        }
+
+        return count;
+    }
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
new file mode 100644
index 0000000..77b5c58
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Network;
+import android.os.SystemClock;
+import android.util.ArraySet;
+
+import com.android.server.connectivity.mdns.util.MdnsLogger;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
+
+import java.io.EOFException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/** A class that decodes mDNS responses from UDP packets. */
+public class MdnsResponseDecoder {
+    public static final int SUCCESS = 0;
+    private static final String TAG = "MdnsResponseDecoder";
+    private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
+    private final boolean allowMultipleSrvRecordsPerHost =
+            MdnsConfigs.allowMultipleSrvRecordsPerHost();
+    @Nullable private final String[] serviceType;
+    private final Clock clock;
+
+    /** Constructs a new decoder that will extract responses for the given service type. */
+    public MdnsResponseDecoder(@NonNull Clock clock, @Nullable String[] serviceType) {
+        this.clock = clock;
+        this.serviceType = serviceType;
+    }
+
+    private static MdnsResponse findResponseWithPointer(
+            List<MdnsResponse> responses, String[] pointer) {
+        if (responses != null) {
+            for (MdnsResponse response : responses) {
+                if (MdnsUtils.equalsDnsLabelIgnoreDnsCase(response.getServiceName(), pointer)) {
+                    return response;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static MdnsResponse findResponseWithHostName(
+            List<MdnsResponse> responses, String[] hostName) {
+        if (responses != null) {
+            for (MdnsResponse response : responses) {
+                MdnsServiceRecord serviceRecord = response.getServiceRecord();
+                if (serviceRecord == null) {
+                    continue;
+                }
+                if (MdnsUtils.equalsDnsLabelIgnoreDnsCase(serviceRecord.getServiceHost(),
+                        hostName)) {
+                    return response;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Decodes all mDNS responses for the desired service type from a packet. The class does not
+     * check the responses for completeness; the caller should do that.
+     *
+     * @param recvbuf The received data buffer to read from.
+     * @param length The length of received data buffer.
+     * @return A decoded {@link MdnsPacket}.
+     * @throws MdnsPacket.ParseException if a response packet could not be parsed.
+     */
+    @NonNull
+    public static MdnsPacket parseResponse(@NonNull byte[] recvbuf, int length)
+            throws MdnsPacket.ParseException {
+        MdnsPacketReader reader = new MdnsPacketReader(recvbuf, length);
+
+        final MdnsPacket mdnsPacket;
+        try {
+            reader.readUInt16(); // transaction ID (not used)
+            int flags = reader.readUInt16();
+            if ((flags & MdnsConstants.FLAGS_RESPONSE_MASK) != MdnsConstants.FLAGS_RESPONSE) {
+                throw new MdnsPacket.ParseException(
+                        MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE, "Not a response", null);
+            }
+
+            mdnsPacket = MdnsPacket.parseRecordsSection(reader, flags);
+            if (mdnsPacket.answers.size() < 1) {
+                throw new MdnsPacket.ParseException(
+                        MdnsResponseErrorCode.ERROR_NO_ANSWERS, "Response has no answers",
+                        null);
+            }
+            return mdnsPacket;
+        } catch (EOFException e) {
+            throw new MdnsPacket.ParseException(MdnsResponseErrorCode.ERROR_END_OF_FILE,
+                    "Reached the end of the mDNS response unexpectedly.", e);
+        }
+    }
+
+    /**
+     * Augments a list of {@link MdnsResponse} with records from a packet. The class does not check
+     * the resulting responses for completeness; the caller should do that.
+     *
+     * @param mdnsPacket the response packet with the new records
+     * @param existingResponses list of existing responses. Will not be modified.
+     * @param interfaceIndex the network interface index (or
+     * {@link MdnsSocket#INTERFACE_INDEX_UNSPECIFIED} if not known) at which the packet was received
+     * @param network the network at which the packet was received, or null if it is unknown.
+     * @return The set of response instances that were modified or newly added.
+     */
+    public ArraySet<MdnsResponse> augmentResponses(@NonNull MdnsPacket mdnsPacket,
+            @NonNull Collection<MdnsResponse> existingResponses, int interfaceIndex,
+            @Nullable Network network) {
+        final ArrayList<MdnsRecord> records = new ArrayList<>(
+                mdnsPacket.questions.size() + mdnsPacket.answers.size()
+                        + mdnsPacket.authorityRecords.size() + mdnsPacket.additionalRecords.size());
+        records.addAll(mdnsPacket.answers);
+        records.addAll(mdnsPacket.authorityRecords);
+        records.addAll(mdnsPacket.additionalRecords);
+
+        final ArraySet<MdnsResponse> modified = new ArraySet<>();
+        final ArrayList<MdnsResponse> responses = new ArrayList<>(existingResponses.size());
+        for (MdnsResponse existing : existingResponses) {
+            responses.add(new MdnsResponse(existing));
+        }
+        // The response records are structured in a hierarchy, where some records reference
+        // others, as follows:
+        //
+        //        PTR
+        //        / \
+        //       /   \
+        //      TXT  SRV
+        //           / \
+        //          /   \
+        //         A   AAAA
+        //
+        // But the order in which these records appear in the response packet is completely
+        // arbitrary. This means that we need to rescan the record list to construct each level of
+        // this hierarchy.
+        //
+        // PTR: service type -> service instance name
+        //
+        // SRV: service instance name -> host name (priority, weight)
+        //
+        // TXT: service instance name -> machine readable txt entries.
+        //
+        // A: host name -> IP address
+
+        // Loop 1: find PTR records, which identify distinct service instances.
+        long now = SystemClock.elapsedRealtime();
+        for (MdnsRecord record : records) {
+            if (record instanceof MdnsPointerRecord) {
+                String[] name = record.getName();
+                if ((serviceType == null) || MdnsUtils.typeEqualsOrIsSubtype(
+                        serviceType, name)) {
+                    MdnsPointerRecord pointerRecord = (MdnsPointerRecord) record;
+                    // Group PTR records that refer to the same service instance name into a single
+                    // response.
+                    MdnsResponse response = findResponseWithPointer(responses,
+                            pointerRecord.getPointer());
+                    if (response == null) {
+                        response = new MdnsResponse(now, pointerRecord.getPointer(), interfaceIndex,
+                                network);
+                        responses.add(response);
+                    }
+
+                    if (response.addPointerRecord((MdnsPointerRecord) record)) {
+                        modified.add(response);
+                    }
+                }
+            }
+        }
+
+        // Loop 2: find SRV and TXT records, which reference the pointer in the PTR record.
+        for (MdnsRecord record : records) {
+            if (record instanceof MdnsServiceRecord) {
+                MdnsServiceRecord serviceRecord = (MdnsServiceRecord) record;
+                MdnsResponse response = findResponseWithPointer(responses, serviceRecord.getName());
+                if (response != null && response.setServiceRecord(serviceRecord)) {
+                    response.dropUnmatchedAddressRecords();
+                    modified.add(response);
+                }
+            } else if (record instanceof MdnsTextRecord) {
+                MdnsTextRecord textRecord = (MdnsTextRecord) record;
+                MdnsResponse response = findResponseWithPointer(responses, textRecord.getName());
+                if (response != null && response.setTextRecord(textRecord)) {
+                    modified.add(response);
+                }
+            }
+        }
+
+        // Loop 3-1: find A and AAAA records and clear addresses if the cache-flush bit set, which
+        //           reference the host name in the SRV record.
+        final List<MdnsInetAddressRecord> inetRecords = new ArrayList<>();
+        for (MdnsRecord record : records) {
+            if (record instanceof MdnsInetAddressRecord) {
+                MdnsInetAddressRecord inetRecord = (MdnsInetAddressRecord) record;
+                inetRecords.add(inetRecord);
+                if (allowMultipleSrvRecordsPerHost) {
+                    List<MdnsResponse> matchingResponses =
+                            findResponsesWithHostName(responses, inetRecord.getName());
+                    for (MdnsResponse response : matchingResponses) {
+                        // Per RFC6762 10.2, clear all address records if the cache-flush bit set.
+                        // This bit, the cache-flush bit, tells neighboring hosts
+                        // that this is not a shared record type.  Instead of merging this new
+                        // record additively into the cache in addition to any previous records with
+                        // the same name, rrtype, and rrclass.
+                        // TODO: All old records with that name, rrtype, and rrclass that were
+                        //       received more than one second ago are declared invalid, and marked
+                        //       to expire from the cache in one second.
+                        if (inetRecord.getCacheFlush()) {
+                            response.clearInet4AddressRecords();
+                            response.clearInet6AddressRecords();
+                        }
+                    }
+                } else {
+                    MdnsResponse response =
+                            findResponseWithHostName(responses, inetRecord.getName());
+                    if (response != null) {
+                        // Per RFC6762 10.2, clear all address records if the cache-flush bit set.
+                        // This bit, the cache-flush bit, tells neighboring hosts
+                        // that this is not a shared record type.  Instead of merging this new
+                        // record additively into the cache in addition to any previous records with
+                        // the same name, rrtype, and rrclass.
+                        // TODO: All old records with that name, rrtype, and rrclass that were
+                        //       received more than one second ago are declared invalid, and marked
+                        //       to expire from the cache in one second.
+                        if (inetRecord.getCacheFlush()) {
+                            response.clearInet4AddressRecords();
+                            response.clearInet6AddressRecords();
+                        }
+                    }
+                }
+            }
+        }
+
+        // Loop 3-2: Assign addresses, which reference the host name in the SRV record.
+        for (MdnsInetAddressRecord inetRecord : inetRecords) {
+            if (allowMultipleSrvRecordsPerHost) {
+                List<MdnsResponse> matchingResponses =
+                        findResponsesWithHostName(responses, inetRecord.getName());
+                for (MdnsResponse response : matchingResponses) {
+                    if (assignInetRecord(response, inetRecord)) {
+                        modified.add(response);
+                    }
+                }
+            } else {
+                MdnsResponse response =
+                        findResponseWithHostName(responses, inetRecord.getName());
+                if (response != null) {
+                    if (assignInetRecord(response, inetRecord)) {
+                        modified.add(response);
+                    }
+                }
+            }
+        }
+
+        return modified;
+    }
+
+    private static boolean assignInetRecord(
+            MdnsResponse response, MdnsInetAddressRecord inetRecord) {
+        if (inetRecord.getInet4Address() != null) {
+            return response.addInet4AddressRecord(inetRecord);
+        } else if (inetRecord.getInet6Address() != null) {
+            return response.addInet6AddressRecord(inetRecord);
+        }
+        return false;
+    }
+
+    private static List<MdnsResponse> findResponsesWithHostName(
+            @Nullable List<MdnsResponse> responses, String[] hostName) {
+        if (responses == null || responses.isEmpty()) {
+            return List.of();
+        }
+
+        List<MdnsResponse> result = null;
+        for (MdnsResponse response : responses) {
+            MdnsServiceRecord serviceRecord = response.getServiceRecord();
+            if (serviceRecord == null) {
+                continue;
+            }
+            if (MdnsUtils.equalsDnsLabelIgnoreDnsCase(serviceRecord.getServiceHost(), hostName)) {
+                if (result == null) {
+                    result = new ArrayList<>(/* initialCapacity= */ responses.size());
+                }
+                result.add(response);
+            }
+        }
+        return result == null ? List.of() : result;
+    }
+
+    public static class Clock {
+        public long elapsedRealtime() {
+            return SystemClock.elapsedRealtime();
+        }
+    }
+}
\ No newline at end of file
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsResponseErrorCode.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseErrorCode.java
similarity index 92%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsResponseErrorCode.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsResponseErrorCode.java
index fcf9058..73a7e3a 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsResponseErrorCode.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseErrorCode.java
@@ -35,4 +35,6 @@
     public static final int ERROR_READING_TXT_RDATA = 10;
     public static final int ERROR_SKIPPING_UNKNOWN_RECORD = 11;
     public static final int ERROR_END_OF_FILE = 12;
+    public static final int ERROR_READING_NSEC_RDATA = 13;
+    public static final int ERROR_READING_ANY_RDATA = 14;
 }
\ No newline at end of file
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsSearchOptions.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
similarity index 85%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsSearchOptions.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
index 583c4a9..3da6bd0 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsSearchOptions.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSearchOptions.java
@@ -46,7 +46,8 @@
                 public MdnsSearchOptions createFromParcel(Parcel source) {
                     return new MdnsSearchOptions(source.createStringArrayList(),
                             source.readBoolean(), source.readBoolean(),
-                            source.readParcelable(null));
+                            source.readParcelable(null),
+                            source.readString());
                 }
 
                 @Override
@@ -56,6 +57,8 @@
             };
     private static MdnsSearchOptions defaultOptions;
     private final List<String> subtypes;
+    @Nullable
+    private final String resolveInstanceName;
 
     private final boolean isPassiveMode;
     private final boolean removeExpiredService;
@@ -64,7 +67,7 @@
 
     /** Parcelable constructs for a {@link MdnsSearchOptions}. */
     MdnsSearchOptions(List<String> subtypes, boolean isPassiveMode, boolean removeExpiredService,
-            @Nullable Network network) {
+            @Nullable Network network, @Nullable String resolveInstanceName) {
         this.subtypes = new ArrayList<>();
         if (subtypes != null) {
             this.subtypes.addAll(subtypes);
@@ -72,6 +75,7 @@
         this.isPassiveMode = isPassiveMode;
         this.removeExpiredService = removeExpiredService;
         mNetwork = network;
+        this.resolveInstanceName = resolveInstanceName;
     }
 
     /** Returns a {@link Builder} for {@link MdnsSearchOptions}. */
@@ -115,6 +119,15 @@
         return mNetwork;
     }
 
+    /**
+     * If non-null, queries should try to resolve all records of this specific service, rather than
+     * discovering all services.
+     */
+    @Nullable
+    public String getResolveInstanceName() {
+        return resolveInstanceName;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -126,6 +139,7 @@
         out.writeBoolean(isPassiveMode);
         out.writeBoolean(removeExpiredService);
         out.writeParcelable(mNetwork, 0);
+        out.writeString(resolveInstanceName);
     }
 
     /** A builder to create {@link MdnsSearchOptions}. */
@@ -134,6 +148,7 @@
         private boolean isPassiveMode = true;
         private boolean removeExpiredService;
         private Network mNetwork;
+        private String resolveInstanceName;
 
         private Builder() {
             subtypes = new ArraySet<>();
@@ -194,10 +209,22 @@
             return this;
         }
 
+        /**
+         * Set the instance name to resolve.
+         *
+         * If non-null, queries should try to resolve all records of this specific service,
+         * rather than discovering all services.
+         * @param name The instance name.
+         */
+        public Builder setResolveInstanceName(String name) {
+            resolveInstanceName = name;
+            return this;
+        }
+
         /** Builds a {@link MdnsSearchOptions} with the arguments supplied to this builder. */
         public MdnsSearchOptions build() {
             return new MdnsSearchOptions(new ArrayList<>(subtypes), isPassiveMode,
-                    removeExpiredService, mNetwork);
+                    removeExpiredService, mNetwork, resolveInstanceName);
         }
     }
 }
\ No newline at end of file
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceBrowserListener.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceBrowserListener.java
similarity index 100%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsServiceBrowserListener.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsServiceBrowserListener.java
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
new file mode 100644
index 0000000..cd0be67
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns;
+
+import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
+import static com.android.server.connectivity.mdns.util.MdnsUtils.equalsIgnoreDnsCase;
+import static com.android.server.connectivity.mdns.util.MdnsUtils.toDnsLowerCase;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Network;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.ArrayMap;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The {@link MdnsServiceCache} manages the service which discovers from each socket and cache these
+ * services to reduce duplicated queries.
+ *
+ * <p>This class is not thread safe, it is intended to be used only from the looper thread.
+ *  However, the constructor is an exception, as it is called on another thread;
+ *  therefore for thread safety all members of this class MUST either be final or initialized
+ *  to their default value (0, false or null).
+ */
+public class MdnsServiceCache {
+    private static class CacheKey {
+        @NonNull final String mLowercaseServiceType;
+        @Nullable final Network mNetwork;
+
+        CacheKey(@NonNull String serviceType, @Nullable Network network) {
+            mLowercaseServiceType = toDnsLowerCase(serviceType);
+            mNetwork = network;
+        }
+
+        @Override public int hashCode() {
+            return Objects.hash(mLowercaseServiceType, mNetwork);
+        }
+
+        @Override public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (!(other instanceof CacheKey)) {
+                return false;
+            }
+            return Objects.equals(mLowercaseServiceType, ((CacheKey) other).mLowercaseServiceType)
+                    && Objects.equals(mNetwork, ((CacheKey) other).mNetwork);
+        }
+    }
+    /**
+     * A map of cached services. Key is composed of service name, type and network. Value is the
+     * service which use the service type to discover from each socket.
+     */
+    @NonNull
+    private final ArrayMap<CacheKey, List<MdnsResponse>> mCachedServices = new ArrayMap<>();
+    @NonNull
+    private final Handler mHandler;
+
+    public MdnsServiceCache(@NonNull Looper looper) {
+        mHandler = new Handler(looper);
+    }
+
+    /**
+     * Get the cache services which are queried from given service type and network.
+     *
+     * @param serviceType the target service type.
+     * @param network the target network
+     * @return the set of services which matches the given service type.
+     */
+    @NonNull
+    public List<MdnsResponse> getCachedServices(@NonNull String serviceType,
+            @Nullable Network network) {
+        ensureRunningOnHandlerThread(mHandler);
+        final CacheKey key = new CacheKey(serviceType, network);
+        return mCachedServices.containsKey(key)
+                ? Collections.unmodifiableList(new ArrayList<>(mCachedServices.get(key)))
+                : Collections.emptyList();
+    }
+
+    private MdnsResponse findMatchedResponse(@NonNull List<MdnsResponse> responses,
+            @NonNull String serviceName) {
+        for (MdnsResponse response : responses) {
+            if (equalsIgnoreDnsCase(serviceName, response.getServiceInstanceName())) {
+                return response;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the cache service.
+     *
+     * @param serviceName the target service name.
+     * @param serviceType the target service type.
+     * @param network the target network
+     * @return the service which matches given conditions.
+     */
+    @Nullable
+    public MdnsResponse getCachedService(@NonNull String serviceName,
+            @NonNull String serviceType, @Nullable Network network) {
+        ensureRunningOnHandlerThread(mHandler);
+        final List<MdnsResponse> responses =
+                mCachedServices.get(new CacheKey(serviceType, network));
+        if (responses == null) {
+            return null;
+        }
+        final MdnsResponse response = findMatchedResponse(responses, serviceName);
+        return response != null ? new MdnsResponse(response) : null;
+    }
+
+    /**
+     * Add or update a service.
+     *
+     * @param serviceType the service type.
+     * @param network the target network
+     * @param response the response of the discovered service.
+     */
+    public void addOrUpdateService(@NonNull String serviceType, @Nullable Network network,
+            @NonNull MdnsResponse response) {
+        ensureRunningOnHandlerThread(mHandler);
+        final List<MdnsResponse> responses = mCachedServices.computeIfAbsent(
+                new CacheKey(serviceType, network), key -> new ArrayList<>());
+        // Remove existing service if present.
+        final MdnsResponse existing =
+                findMatchedResponse(responses, response.getServiceInstanceName());
+        responses.remove(existing);
+        responses.add(response);
+    }
+
+    /**
+     * Remove a service which matches the given service name, type and network.
+     *
+     * @param serviceName the target service name.
+     * @param serviceType the target service type.
+     * @param network the target network.
+     */
+    @Nullable
+    public MdnsResponse removeService(@NonNull String serviceName, @NonNull String serviceType,
+            @Nullable Network network) {
+        ensureRunningOnHandlerThread(mHandler);
+        final List<MdnsResponse> responses =
+                mCachedServices.get(new CacheKey(serviceType, network));
+        if (responses == null) {
+            return null;
+        }
+        final Iterator<MdnsResponse> iterator = responses.iterator();
+        while (iterator.hasNext()) {
+            final MdnsResponse response = iterator.next();
+            if (equalsIgnoreDnsCase(serviceName, response.getServiceInstanceName())) {
+                iterator.remove();
+                return response;
+            }
+        }
+        return null;
+    }
+
+    // TODO: check ttl expiration for each service and notify to the clients.
+}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceInfo.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
similarity index 85%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsServiceInfo.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
index 938fc3f..78df6df 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceInfo.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
@@ -31,10 +31,10 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.TreeMap;
 
 /**
  * A class representing a discovered mDNS service instance.
@@ -57,8 +57,8 @@
                             source.createStringArrayList(),
                             source.createStringArray(),
                             source.readInt(),
-                            source.readString(),
-                            source.readString(),
+                            source.createStringArrayList(),
+                            source.createStringArrayList(),
                             source.createStringArrayList(),
                             source.createTypedArrayList(TextEntry.CREATOR),
                             source.readInt(),
@@ -76,10 +76,10 @@
     private final List<String> subtypes;
     private final String[] hostName;
     private final int port;
-    @Nullable
-    private final String ipv4Address;
-    @Nullable
-    private final String ipv6Address;
+    @NonNull
+    private final List<String> ipv4Addresses;
+    @NonNull
+    private final List<String> ipv6Addresses;
     final List<String> textStrings;
     @Nullable
     final List<TextEntry> textEntries;
@@ -105,8 +105,8 @@
                 subtypes,
                 hostName,
                 port,
-                ipv4Address,
-                ipv6Address,
+                List.of(ipv4Address),
+                List.of(ipv6Address),
                 textStrings,
                 /* textEntries= */ null,
                 /* interfaceIndex= */ INTERFACE_INDEX_UNSPECIFIED,
@@ -130,8 +130,8 @@
                 subtypes,
                 hostName,
                 port,
-                ipv4Address,
-                ipv6Address,
+                List.of(ipv4Address),
+                List.of(ipv6Address),
                 textStrings,
                 textEntries,
                 /* interfaceIndex= */ INTERFACE_INDEX_UNSPECIFIED,
@@ -160,8 +160,8 @@
                 subtypes,
                 hostName,
                 port,
-                ipv4Address,
-                ipv6Address,
+                List.of(ipv4Address),
+                List.of(ipv6Address),
                 textStrings,
                 textEntries,
                 interfaceIndex,
@@ -179,8 +179,8 @@
             @Nullable List<String> subtypes,
             String[] hostName,
             int port,
-            @Nullable String ipv4Address,
-            @Nullable String ipv6Address,
+            @NonNull List<String> ipv4Addresses,
+            @NonNull List<String> ipv6Addresses,
             @Nullable List<String> textStrings,
             @Nullable List<TextEntry> textEntries,
             int interfaceIndex,
@@ -193,8 +193,8 @@
         }
         this.hostName = hostName;
         this.port = port;
-        this.ipv4Address = ipv4Address;
-        this.ipv6Address = ipv6Address;
+        this.ipv4Addresses = new ArrayList<>(ipv4Addresses);
+        this.ipv6Addresses = new ArrayList<>(ipv6Addresses);
         this.textStrings = new ArrayList<>();
         if (textStrings != null) {
             this.textStrings.addAll(textStrings);
@@ -205,17 +205,14 @@
         // compatibility. We should prefer only {@code textEntries} if it's not null.
         List<TextEntry> entries =
                 (this.textEntries != null) ? this.textEntries : parseTextStrings(this.textStrings);
-        Map<String, byte[]> attributes = new HashMap<>(entries.size());
+        // The map of attributes is case-insensitive.
+        final Map<String, byte[]> attributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
         for (TextEntry entry : entries) {
-            String key = entry.getKey().toLowerCase(Locale.ENGLISH);
-
             // Per https://datatracker.ietf.org/doc/html/rfc6763#section-6.4, only the first entry
             // of the same key should be accepted:
             // If a client receives a TXT record containing the same key more than once, then the
             // client MUST silently ignore all but the first occurrence of that attribute.
-            if (!attributes.containsKey(key)) {
-                attributes.put(key, entry.getValue());
-            }
+            attributes.putIfAbsent(entry.getKey(), entry.getValue());
         }
         this.attributes = Collections.unmodifiableMap(attributes);
         this.interfaceIndex = interfaceIndex;
@@ -263,16 +260,41 @@
         return port;
     }
 
-    /** Returns the IPV4 address of this service instance. */
+    /** Returns the IPV4 addresses of this service instance. */
+    @NonNull
+    public List<String> getIpv4Addresses() {
+        return Collections.unmodifiableList(ipv4Addresses);
+    }
+
+    /**
+     * Returns the first IPV4 address of this service instance.
+     *
+     * @deprecated Use {@link #getIpv4Addresses()} to get the entire list of IPV4
+     * addresses for
+     * the host.
+     */
     @Nullable
+    @Deprecated
     public String getIpv4Address() {
-        return ipv4Address;
+        return ipv4Addresses.isEmpty() ? null : ipv4Addresses.get(0);
     }
 
     /** Returns the IPV6 address of this service instance. */
+    @NonNull
+    public List<String> getIpv6Addresses() {
+        return Collections.unmodifiableList(ipv6Addresses);
+    }
+
+    /**
+     * Returns the first IPV6 address of this service instance.
+     *
+     * @deprecated Use {@link #getIpv6Addresses()} to get the entire list of IPV6 addresses for
+     * the host.
+     */
     @Nullable
+    @Deprecated
     public String getIpv6Address() {
-        return ipv6Address;
+        return ipv6Addresses.isEmpty() ? null : ipv6Addresses.get(0);
     }
 
     /**
@@ -311,12 +333,12 @@
      */
     @Nullable
     public byte[] getAttributeAsBytes(@NonNull String key) {
-        return attributes.get(key.toLowerCase(Locale.ENGLISH));
+        return attributes.get(key);
     }
 
     /** Returns an immutable map of all attributes. */
     public Map<String, String> getAttributes() {
-        Map<String, String> map = new HashMap<>(attributes.size());
+        Map<String, String> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
         for (Map.Entry<String, byte[]> kv : attributes.entrySet()) {
             final byte[] value = kv.getValue();
             map.put(kv.getKey(), value == null ? null : new String(value, UTF_8));
@@ -336,8 +358,8 @@
         out.writeStringList(subtypes);
         out.writeStringArray(hostName);
         out.writeInt(port);
-        out.writeString(ipv4Address);
-        out.writeString(ipv6Address);
+        out.writeStringList(ipv4Addresses);
+        out.writeStringList(ipv6Addresses);
         out.writeStringList(textStrings);
         out.writeTypedList(textEntries);
         out.writeInt(interfaceIndex);
@@ -346,13 +368,16 @@
 
     @Override
     public String toString() {
-        return String.format(
-                Locale.ROOT,
-                "Name: %s, subtypes: %s, ip: %s, port: %d",
-                serviceInstanceName,
-                TextUtils.join(",", subtypes),
-                ipv4Address,
-                port);
+        return "Name: " + serviceInstanceName
+                + ", type: " + TextUtils.join(".", serviceType)
+                + ", subtypes: " + TextUtils.join(",", subtypes)
+                + ", ip: " + ipv4Addresses
+                + ", ipv6: " + ipv6Addresses
+                + ", port: " + port
+                + ", interfaceIndex: " + interfaceIndex
+                + ", network: " + network
+                + ", textStrings: " + textStrings
+                + ", textEntries: " + textEntries;
     }
 
 
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceRecord.java
similarity index 94%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsServiceRecord.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsServiceRecord.java
index ebd8b77..f851b35 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceRecord.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
 
 import java.io.IOException;
 import java.util.Arrays;
@@ -142,7 +143,8 @@
     @Override
     public int hashCode() {
         return (super.hashCode() * 31)
-                + Objects.hash(servicePriority, serviceWeight, Arrays.hashCode(serviceHost),
+                + Objects.hash(servicePriority, serviceWeight,
+                Arrays.hashCode(MdnsUtils.toDnsLabelsLowerCase(serviceHost)),
                 servicePort);
     }
 
@@ -159,7 +161,7 @@
         return super.equals(other)
                 && (servicePriority == otherRecord.servicePriority)
                 && (serviceWeight == otherRecord.serviceWeight)
-                && Arrays.equals(serviceHost, otherRecord.serviceHost)
+                && MdnsUtils.equalsDnsLabelIgnoreDnsCase(serviceHost, otherRecord.serviceHost)
                 && (servicePort == otherRecord.servicePort);
     }
 }
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
new file mode 100644
index 0000000..c2c0db2
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -0,0 +1,612 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Network;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Pair;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * Instance of this class sends and receives mDNS packets of a given service type and invoke
+ * registered {@link MdnsServiceBrowserListener} instances.
+ */
+public class MdnsServiceTypeClient {
+
+    private static final int DEFAULT_MTU = 1500;
+
+    private final String serviceType;
+    private final String[] serviceTypeLabels;
+    private final MdnsSocketClientBase socketClient;
+    private final MdnsResponseDecoder responseDecoder;
+    private final ScheduledExecutorService executor;
+    @Nullable private final Network network;
+    @NonNull private final SharedLog sharedLog;
+    private final Object lock = new Object();
+    private final ArrayMap<MdnsServiceBrowserListener, MdnsSearchOptions> listeners =
+            new ArrayMap<>();
+    private final Map<String, MdnsResponse> instanceNameToResponse = new HashMap<>();
+    private final boolean removeServiceAfterTtlExpires =
+            MdnsConfigs.removeServiceAfterTtlExpires();
+    private final boolean allowSearchOptionsToRemoveExpiredService =
+            MdnsConfigs.allowSearchOptionsToRemoveExpiredService();
+
+    private final MdnsResponseDecoder.Clock clock;
+
+    @Nullable private MdnsSearchOptions searchOptions;
+
+    // The session ID increases when startSendAndReceive() is called where we schedule a
+    // QueryTask for
+    // new subtypes. It stays the same between packets for same subtypes.
+    private long currentSessionId = 0;
+
+    @GuardedBy("lock")
+    @Nullable
+    private Future<?> requestTaskFuture;
+
+    /**
+     * Constructor of {@link MdnsServiceTypeClient}.
+     *
+     * @param socketClient Sends and receives mDNS packet.
+     * @param executor         A {@link ScheduledExecutorService} used to schedule query tasks.
+     */
+    public MdnsServiceTypeClient(
+            @NonNull String serviceType,
+            @NonNull MdnsSocketClientBase socketClient,
+            @NonNull ScheduledExecutorService executor,
+            @Nullable Network network,
+            @NonNull SharedLog sharedLog) {
+        this(serviceType, socketClient, executor, new MdnsResponseDecoder.Clock(), network,
+                sharedLog);
+    }
+
+    @VisibleForTesting
+    public MdnsServiceTypeClient(
+            @NonNull String serviceType,
+            @NonNull MdnsSocketClientBase socketClient,
+            @NonNull ScheduledExecutorService executor,
+            @NonNull MdnsResponseDecoder.Clock clock,
+            @Nullable Network network,
+            @NonNull SharedLog sharedLog) {
+        this.serviceType = serviceType;
+        this.socketClient = socketClient;
+        this.executor = executor;
+        this.serviceTypeLabels = TextUtils.split(serviceType, "\\.");
+        this.responseDecoder = new MdnsResponseDecoder(clock, serviceTypeLabels);
+        this.clock = clock;
+        this.network = network;
+        this.sharedLog = sharedLog;
+    }
+
+    private static MdnsServiceInfo buildMdnsServiceInfoFromResponse(
+            @NonNull MdnsResponse response, @NonNull String[] serviceTypeLabels) {
+        String[] hostName = null;
+        int port = 0;
+        if (response.hasServiceRecord()) {
+            hostName = response.getServiceRecord().getServiceHost();
+            port = response.getServiceRecord().getServicePort();
+        }
+
+        final List<String> ipv4Addresses = new ArrayList<>();
+        final List<String> ipv6Addresses = new ArrayList<>();
+        if (response.hasInet4AddressRecord()) {
+            for (MdnsInetAddressRecord inetAddressRecord : response.getInet4AddressRecords()) {
+                final Inet4Address inet4Address = inetAddressRecord.getInet4Address();
+                ipv4Addresses.add((inet4Address == null) ? null : inet4Address.getHostAddress());
+            }
+        }
+        if (response.hasInet6AddressRecord()) {
+            for (MdnsInetAddressRecord inetAddressRecord : response.getInet6AddressRecords()) {
+                final Inet6Address inet6Address = inetAddressRecord.getInet6Address();
+                ipv6Addresses.add((inet6Address == null) ? null : inet6Address.getHostAddress());
+            }
+        }
+        String serviceInstanceName = response.getServiceInstanceName();
+        if (serviceInstanceName == null) {
+            throw new IllegalStateException(
+                    "mDNS response must have non-null service instance name");
+        }
+        List<String> textStrings = null;
+        List<MdnsServiceInfo.TextEntry> textEntries = null;
+        if (response.hasTextRecord()) {
+            textStrings = response.getTextRecord().getStrings();
+            textEntries = response.getTextRecord().getEntries();
+        }
+        // TODO: Throw an error message if response doesn't have Inet6 or Inet4 address.
+        return new MdnsServiceInfo(
+                serviceInstanceName,
+                serviceTypeLabels,
+                response.getSubtypes(),
+                hostName,
+                port,
+                ipv4Addresses,
+                ipv6Addresses,
+                textStrings,
+                textEntries,
+                response.getInterfaceIndex(),
+                response.getNetwork());
+    }
+
+    /**
+     * Registers {@code listener} for receiving discovery event of mDNS service instances, and
+     * starts
+     * (or continue) to send mDNS queries periodically.
+     *
+     * @param listener      The {@link MdnsServiceBrowserListener} to register.
+     * @param searchOptions {@link MdnsSearchOptions} contains the list of subtypes to discover.
+     */
+    public void startSendAndReceive(
+            @NonNull MdnsServiceBrowserListener listener,
+            @NonNull MdnsSearchOptions searchOptions) {
+        synchronized (lock) {
+            this.searchOptions = searchOptions;
+            boolean hadReply = false;
+            if (listeners.put(listener, searchOptions) == null) {
+                for (MdnsResponse existingResponse : instanceNameToResponse.values()) {
+                    if (!responseMatchesOptions(existingResponse, searchOptions)) continue;
+                    final MdnsServiceInfo info =
+                            buildMdnsServiceInfoFromResponse(existingResponse, serviceTypeLabels);
+                    listener.onServiceNameDiscovered(info);
+                    if (existingResponse.isComplete()) {
+                        listener.onServiceFound(info);
+                        hadReply = true;
+                    }
+                }
+            }
+            // Cancel the next scheduled periodical task.
+            if (requestTaskFuture != null) {
+                requestTaskFuture.cancel(true);
+            }
+            // Keep tracking the ScheduledFuture for the task so we can cancel it if caller is not
+            // interested anymore.
+            final QueryTaskConfig taskConfig = new QueryTaskConfig(
+                    searchOptions.getSubtypes(),
+                    searchOptions.isPassiveMode(),
+                    ++currentSessionId,
+                    network);
+            if (hadReply) {
+                requestTaskFuture = scheduleNextRunLocked(taskConfig);
+            } else {
+                requestTaskFuture = executor.submit(new QueryTask(taskConfig));
+            }
+        }
+    }
+
+    private boolean responseMatchesOptions(@NonNull MdnsResponse response,
+            @NonNull MdnsSearchOptions options) {
+        final boolean matchesInstanceName = options.getResolveInstanceName() == null
+                // DNS is case-insensitive, so ignore case in the comparison
+                || MdnsUtils.equalsIgnoreDnsCase(options.getResolveInstanceName(),
+                        response.getServiceInstanceName());
+
+        // If discovery is requiring some subtypes, the response must have one that matches a
+        // requested one.
+        final List<String> responseSubtypes = response.getSubtypes() == null
+                ? Collections.emptyList() : response.getSubtypes();
+        final boolean matchesSubtype = options.getSubtypes().size() == 0
+                || CollectionUtils.any(options.getSubtypes(), requiredSub ->
+                CollectionUtils.any(responseSubtypes, actualSub ->
+                        MdnsUtils.equalsIgnoreDnsCase(
+                                MdnsConstants.SUBTYPE_PREFIX + requiredSub, actualSub)));
+
+        return matchesInstanceName && matchesSubtype;
+    }
+
+    /**
+     * Unregisters {@code listener} from receiving discovery event of mDNS service instances.
+     *
+     * @param listener The {@link MdnsServiceBrowserListener} to unregister.
+     * @return {@code true} if no listener is registered with this client after unregistering {@code
+     * listener}. Otherwise returns {@code false}.
+     */
+    public boolean stopSendAndReceive(@NonNull MdnsServiceBrowserListener listener) {
+        synchronized (lock) {
+            if (listeners.remove(listener) == null) {
+                return listeners.isEmpty();
+            }
+            if (listeners.isEmpty() && requestTaskFuture != null) {
+                requestTaskFuture.cancel(true);
+                requestTaskFuture = null;
+            }
+            return listeners.isEmpty();
+        }
+    }
+
+    public String[] getServiceTypeLabels() {
+        return serviceTypeLabels;
+    }
+
+    /**
+     * Process an incoming response packet.
+     */
+    public synchronized void processResponse(@NonNull MdnsPacket packet, int interfaceIndex,
+            Network network) {
+        synchronized (lock) {
+            // Augment the list of current known responses, and generated responses for resolve
+            // requests if there is no known response
+            final List<MdnsResponse> currentList = new ArrayList<>(instanceNameToResponse.values());
+            currentList.addAll(makeResponsesForResolveIfUnknown(interfaceIndex, network));
+            final ArraySet<MdnsResponse> modifiedResponses = responseDecoder.augmentResponses(
+                    packet, currentList, interfaceIndex, network);
+
+            for (MdnsResponse modified : modifiedResponses) {
+                if (modified.isGoodbye()) {
+                    onGoodbyeReceived(modified.getServiceInstanceName());
+                } else {
+                    onResponseModified(modified);
+                }
+            }
+        }
+    }
+
+    public synchronized void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode) {
+        for (int i = 0; i < listeners.size(); i++) {
+            listeners.keyAt(i).onFailedToParseMdnsResponse(receivedPacketNumber, errorCode);
+        }
+    }
+
+    /** Notify all services are removed because the socket is destroyed. */
+    public void notifySocketDestroyed() {
+        synchronized (lock) {
+            for (MdnsResponse response : instanceNameToResponse.values()) {
+                final String name = response.getServiceInstanceName();
+                if (name == null) continue;
+                for (int i = 0; i < listeners.size(); i++) {
+                    if (!responseMatchesOptions(response, listeners.valueAt(i))) continue;
+                    final MdnsServiceBrowserListener listener = listeners.keyAt(i);
+                    final MdnsServiceInfo serviceInfo =
+                            buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
+                    if (response.isComplete()) {
+                        sharedLog.log("Socket destroyed. onServiceRemoved: " + name);
+                        listener.onServiceRemoved(serviceInfo);
+                    }
+                    sharedLog.log("Socket destroyed. onServiceNameRemoved: " + name);
+                    listener.onServiceNameRemoved(serviceInfo);
+                }
+            }
+
+            if (requestTaskFuture != null) {
+                requestTaskFuture.cancel(true);
+                requestTaskFuture = null;
+            }
+        }
+    }
+
+    private void onResponseModified(@NonNull MdnsResponse response) {
+        final String serviceInstanceName = response.getServiceInstanceName();
+        final MdnsResponse currentResponse =
+                instanceNameToResponse.get(serviceInstanceName);
+
+        boolean newServiceFound = false;
+        boolean serviceBecomesComplete = false;
+        if (currentResponse == null) {
+            newServiceFound = true;
+            if (serviceInstanceName != null) {
+                instanceNameToResponse.put(serviceInstanceName, response);
+            }
+        } else {
+            boolean before = currentResponse.isComplete();
+            instanceNameToResponse.put(serviceInstanceName, response);
+            boolean after = response.isComplete();
+            serviceBecomesComplete = !before && after;
+        }
+        MdnsServiceInfo serviceInfo =
+                buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
+
+        for (int i = 0; i < listeners.size(); i++) {
+            if (!responseMatchesOptions(response, listeners.valueAt(i))) continue;
+            final MdnsServiceBrowserListener listener = listeners.keyAt(i);
+            if (newServiceFound) {
+                sharedLog.log("onServiceNameDiscovered: " + serviceInfo);
+                listener.onServiceNameDiscovered(serviceInfo);
+            }
+
+            if (response.isComplete()) {
+                if (newServiceFound || serviceBecomesComplete) {
+                    sharedLog.log("onServiceFound: " + serviceInfo);
+                    listener.onServiceFound(serviceInfo);
+                } else {
+                    sharedLog.log("onServiceUpdated: " + serviceInfo);
+                    listener.onServiceUpdated(serviceInfo);
+                }
+            }
+        }
+    }
+
+    private void onGoodbyeReceived(@Nullable String serviceInstanceName) {
+        final MdnsResponse response = instanceNameToResponse.remove(serviceInstanceName);
+        if (response == null) {
+            return;
+        }
+        for (int i = 0; i < listeners.size(); i++) {
+            if (!responseMatchesOptions(response, listeners.valueAt(i))) continue;
+            final MdnsServiceBrowserListener listener = listeners.keyAt(i);
+            final MdnsServiceInfo serviceInfo =
+                    buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
+            if (response.isComplete()) {
+                sharedLog.log("onServiceRemoved: " + serviceInfo);
+                listener.onServiceRemoved(serviceInfo);
+            }
+            sharedLog.log("onServiceNameRemoved: " + serviceInfo);
+            listener.onServiceNameRemoved(serviceInfo);
+        }
+    }
+
+    private boolean shouldRemoveServiceAfterTtlExpires() {
+        if (removeServiceAfterTtlExpires) {
+            return true;
+        }
+        return allowSearchOptionsToRemoveExpiredService
+                && searchOptions != null
+                && searchOptions.removeExpiredService();
+    }
+
+    @VisibleForTesting
+    MdnsPacketWriter createMdnsPacketWriter() {
+        return new MdnsPacketWriter(DEFAULT_MTU);
+    }
+
+    // A configuration for the PeriodicalQueryTask that contains parameters to build a query packet.
+    // Call to getConfigForNextRun returns a config that can be used to build the next query task.
+    @VisibleForTesting
+    static class QueryTaskConfig {
+
+        private static final int INITIAL_TIME_BETWEEN_BURSTS_MS =
+                (int) MdnsConfigs.initialTimeBetweenBurstsMs();
+        private static final int TIME_BETWEEN_BURSTS_MS = (int) MdnsConfigs.timeBetweenBurstsMs();
+        private static final int QUERIES_PER_BURST = (int) MdnsConfigs.queriesPerBurst();
+        private static final int TIME_BETWEEN_QUERIES_IN_BURST_MS =
+                (int) MdnsConfigs.timeBetweenQueriesInBurstMs();
+        private static final int QUERIES_PER_BURST_PASSIVE_MODE =
+                (int) MdnsConfigs.queriesPerBurstPassive();
+        private static final int UNSIGNED_SHORT_MAX_VALUE = 65536;
+        // The following fields are used by QueryTask so we need to test them.
+        @VisibleForTesting
+        final List<String> subtypes;
+        private final boolean alwaysAskForUnicastResponse =
+                MdnsConfigs.alwaysAskForUnicastResponseInEachBurst();
+        private final boolean usePassiveMode;
+        private final long sessionId;
+        @VisibleForTesting
+        int transactionId;
+        @VisibleForTesting
+        boolean expectUnicastResponse;
+        private int queriesPerBurst;
+        private int timeBetweenBurstsInMs;
+        private int burstCounter;
+        private int timeToRunNextTaskInMs;
+        private boolean isFirstBurst;
+        @Nullable private final Network network;
+
+        QueryTaskConfig(@NonNull Collection<String> subtypes, boolean usePassiveMode,
+                long sessionId, @Nullable Network network) {
+            this.usePassiveMode = usePassiveMode;
+            this.subtypes = new ArrayList<>(subtypes);
+            this.queriesPerBurst = QUERIES_PER_BURST;
+            this.burstCounter = 0;
+            this.transactionId = 1;
+            this.expectUnicastResponse = true;
+            this.isFirstBurst = true;
+            this.sessionId = sessionId;
+            // Config the scan frequency based on the scan mode.
+            if (this.usePassiveMode) {
+                // In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and then
+                // in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
+                // queries.
+                this.timeBetweenBurstsInMs = TIME_BETWEEN_BURSTS_MS;
+            } else {
+                // In active scan mode, sends a burst of QUERIES_PER_BURST queries,
+                // TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
+                // then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
+                // doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
+                this.timeBetweenBurstsInMs = INITIAL_TIME_BETWEEN_BURSTS_MS;
+            }
+            this.network = network;
+        }
+
+        QueryTaskConfig getConfigForNextRun() {
+            if (++transactionId > UNSIGNED_SHORT_MAX_VALUE) {
+                transactionId = 1;
+            }
+            // Only the first query expects uni-cast response.
+            expectUnicastResponse = false;
+            if (++burstCounter == queriesPerBurst) {
+                burstCounter = 0;
+
+                if (alwaysAskForUnicastResponse) {
+                    expectUnicastResponse = true;
+                }
+                // In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and
+                // then in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
+                // queries.
+                if (isFirstBurst) {
+                    isFirstBurst = false;
+                    if (usePassiveMode) {
+                        queriesPerBurst = QUERIES_PER_BURST_PASSIVE_MODE;
+                    }
+                }
+                // In active scan mode, sends a burst of QUERIES_PER_BURST queries,
+                // TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
+                // then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
+                // doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
+                timeToRunNextTaskInMs = timeBetweenBurstsInMs;
+                if (timeBetweenBurstsInMs < TIME_BETWEEN_BURSTS_MS) {
+                    timeBetweenBurstsInMs = Math.min(timeBetweenBurstsInMs * 2,
+                            TIME_BETWEEN_BURSTS_MS);
+                }
+            } else {
+                timeToRunNextTaskInMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
+            }
+            return this;
+        }
+    }
+
+    private List<MdnsResponse> makeResponsesForResolveIfUnknown(int interfaceIndex,
+            @NonNull Network network) {
+        final List<MdnsResponse> resolveResponses = new ArrayList<>();
+        for (int i = 0; i < listeners.size(); i++) {
+            final String resolveName = listeners.valueAt(i).getResolveInstanceName();
+            if (resolveName == null) {
+                continue;
+            }
+            MdnsResponse knownResponse = instanceNameToResponse.get(resolveName);
+            if (knownResponse == null) {
+                final ArrayList<String> instanceFullName = new ArrayList<>(
+                        serviceTypeLabels.length + 1);
+                instanceFullName.add(resolveName);
+                instanceFullName.addAll(Arrays.asList(serviceTypeLabels));
+                knownResponse = new MdnsResponse(
+                        0L /* lastUpdateTime */, instanceFullName.toArray(new String[0]),
+                        interfaceIndex, network);
+            }
+            resolveResponses.add(knownResponse);
+        }
+        return resolveResponses;
+    }
+
+    // A FutureTask that enqueues a single query, and schedule a new FutureTask for the next task.
+    private class QueryTask implements Runnable {
+
+        private final QueryTaskConfig config;
+
+        QueryTask(@NonNull QueryTaskConfig config) {
+            this.config = config;
+        }
+
+        @Override
+        public void run() {
+            final List<MdnsResponse> servicesToResolve;
+            final boolean sendDiscoveryQueries;
+            synchronized (lock) {
+                // The listener is requesting to resolve a service that has no info in
+                // cache. Use the provided name to generate a minimal response, so other records are
+                // queried to complete it.
+                // Only the names are used to know which queries to send, other parameters like
+                // interfaceIndex do not matter.
+                servicesToResolve = makeResponsesForResolveIfUnknown(
+                        0 /* interfaceIndex */, config.network);
+                sendDiscoveryQueries = servicesToResolve.size() < listeners.size();
+            }
+            Pair<Integer, List<String>> result;
+            try {
+                result =
+                        new EnqueueMdnsQueryCallable(
+                                socketClient,
+                                createMdnsPacketWriter(),
+                                serviceType,
+                                config.subtypes,
+                                config.expectUnicastResponse,
+                                config.transactionId,
+                                config.network,
+                                sendDiscoveryQueries,
+                                servicesToResolve)
+                                .call();
+            } catch (RuntimeException e) {
+                sharedLog.e(String.format("Failed to run EnqueueMdnsQueryCallable for subtype: %s",
+                        TextUtils.join(",", config.subtypes)), e);
+                result = null;
+            }
+            synchronized (lock) {
+                if (MdnsConfigs.useSessionIdToScheduleMdnsTask()) {
+                    // In case that the task is not canceled successfully, use session ID to check
+                    // if this task should continue to schedule more.
+                    if (config.sessionId != currentSessionId) {
+                        return;
+                    }
+                }
+
+                if (MdnsConfigs.shouldCancelScanTaskWhenFutureIsNull()) {
+                    if (requestTaskFuture == null) {
+                        // If requestTaskFuture is set to null, the task is cancelled. We can't use
+                        // isCancelled() here because this QueryTask is different from the future
+                        // that is returned from executor.schedule(). See b/71646910.
+                        return;
+                    }
+                }
+                if ((result != null)) {
+                    for (int i = 0; i < listeners.size(); i++) {
+                        listeners.keyAt(i).onDiscoveryQuerySent(result.second, result.first);
+                    }
+                }
+                if (shouldRemoveServiceAfterTtlExpires()) {
+                    Iterator<MdnsResponse> iter = instanceNameToResponse.values().iterator();
+                    while (iter.hasNext()) {
+                        MdnsResponse existingResponse = iter.next();
+                        if (existingResponse.hasServiceRecord()
+                                && existingResponse
+                                .getServiceRecord()
+                                .getRemainingTTL(clock.elapsedRealtime())
+                                == 0) {
+                            iter.remove();
+                            for (int i = 0; i < listeners.size(); i++) {
+                                if (!responseMatchesOptions(existingResponse,
+                                        listeners.valueAt(i)))  {
+                                    continue;
+                                }
+                                final MdnsServiceBrowserListener listener = listeners.keyAt(i);
+                                if (existingResponse.getServiceInstanceName() != null) {
+                                    final MdnsServiceInfo serviceInfo =
+                                            buildMdnsServiceInfoFromResponse(
+                                                    existingResponse, serviceTypeLabels);
+                                    if (existingResponse.isComplete()) {
+                                        sharedLog.log("TTL expired. onServiceRemoved: "
+                                                + serviceInfo);
+                                        listener.onServiceRemoved(serviceInfo);
+                                    }
+                                    sharedLog.log("TTL expired. onServiceNameRemoved: "
+                                            + serviceInfo);
+                                    listener.onServiceNameRemoved(serviceInfo);
+                                }
+                            }
+                        }
+                    }
+                }
+                requestTaskFuture = scheduleNextRunLocked(this.config);
+            }
+        }
+    }
+
+    @NonNull
+    private Future<?> scheduleNextRunLocked(@NonNull QueryTaskConfig lastRunConfig) {
+        QueryTaskConfig config = lastRunConfig.getConfigForNextRun();
+        return executor.schedule(new QueryTask(config), config.timeToRunNextTaskInMs, MILLISECONDS);
+    }
+}
\ No newline at end of file
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsSocket.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java
similarity index 97%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsSocket.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java
index 64c4495..5fd1354 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsSocket.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocket.java
@@ -40,9 +40,9 @@
     private static final MdnsLogger LOGGER = new MdnsLogger("MdnsSocket");
 
     static final int INTERFACE_INDEX_UNSPECIFIED = -1;
-    protected static final InetSocketAddress MULTICAST_IPV4_ADDRESS =
+    public static final InetSocketAddress MULTICAST_IPV4_ADDRESS =
             new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT);
-    protected static final InetSocketAddress MULTICAST_IPV6_ADDRESS =
+    public static final InetSocketAddress MULTICAST_IPV6_ADDRESS =
             new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT);
     private final MulticastNetworkInterfaceProvider multicastNetworkInterfaceProvider;
     private final MulticastSocket multicastSocket;
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
similarity index 91%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsSocketClient.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
index 6a321d1..1144d16 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
@@ -33,7 +33,6 @@
 import java.net.DatagramPacket;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Queue;
 import java.util.Timer;
@@ -47,7 +46,7 @@
  *
  * <p>See https://tools.ietf.org/html/rfc6763 (namely sections 4 and 5).
  */
-public class MdnsSocketClient {
+public class MdnsSocketClient implements MdnsSocketClientBase {
 
     private static final String TAG = "MdnsClient";
     // TODO: The following values are copied from cast module. We need to think about the
@@ -71,7 +70,6 @@
     private final Context context;
     private final byte[] multicastReceiverBuffer = new byte[RECEIVER_BUFFER_SIZE];
     @Nullable private final byte[] unicastReceiverBuffer;
-    private final MdnsResponseDecoder responseDecoder;
     private final MulticastLock multicastLock;
     private final boolean useSeparateSocketForUnicast =
             MdnsConfigs.useSeparateSocketToSendUnicastQuery();
@@ -108,7 +106,6 @@
     public MdnsSocketClient(@NonNull Context context, @NonNull MulticastLock multicastLock) {
         this.context = context;
         this.multicastLock = multicastLock;
-        responseDecoder = new MdnsResponseDecoder(new MdnsResponseDecoder.Clock(), null);
         if (useSeparateSocketForUnicast) {
             unicastReceiverBuffer = new byte[RECEIVER_BUFFER_SIZE];
         } else {
@@ -116,11 +113,13 @@
         }
     }
 
+    @Override
     public synchronized void setCallback(@Nullable Callback callback) {
         this.callback = callback;
     }
 
     @RequiresPermission(permission.CHANGE_WIFI_MULTICAST_STATE)
+    @Override
     public synchronized void startDiscovery() throws IOException {
         if (multicastSocket != null) {
             LOGGER.w("Discovery is already in progress.");
@@ -160,6 +159,7 @@
     }
 
     @RequiresPermission(permission.CHANGE_WIFI_MULTICAST_STATE)
+    @Override
     public void stopDiscovery() {
         LOGGER.log("Stop discovery.");
         if (multicastSocket == null && unicastSocket == null) {
@@ -208,6 +208,36 @@
         }
     }
 
+    @Override
+    public void sendMulticastPacket(@NonNull DatagramPacket packet, @Nullable Network network) {
+        if (network != null) {
+            throw new IllegalArgumentException("This socket client does not support sending to "
+                    + "specific networks");
+        }
+        sendMulticastPacket(packet);
+    }
+
+    @Override
+    public void sendUnicastPacket(@NonNull DatagramPacket packet, @Nullable Network network) {
+        if (network != null) {
+            throw new IllegalArgumentException("This socket client does not support sending to "
+                    + "specific networks");
+        }
+        sendUnicastPacket(packet);
+    }
+
+    @Override
+    public void notifyNetworkRequested(
+            @NonNull MdnsServiceBrowserListener listener,
+            @Nullable Network network,
+            @NonNull SocketCreationCallback socketCreationCallback) {
+        if (network != null) {
+            throw new IllegalArgumentException("This socket client does not support requesting "
+                    + "specific networks");
+        }
+        socketCreationCallback.onSocketCreated(null);
+    }
+
     private void sendMdnsPacket(DatagramPacket packet, Queue<DatagramPacket> packetQueueToUse) {
         if (shouldStopSocketLoop && !MdnsConfigs.allowAddMdnsPacketAfterDiscoveryStops()) {
             LOGGER.w("sendMdnsPacket() is called after discovery already stopped");
@@ -414,37 +444,27 @@
             int interfaceIndex, @Nullable Network network) {
         int packetNumber = ++receivedPacketNumber;
 
-        List<MdnsResponse> responses = new LinkedList<>();
-        int errorCode = responseDecoder.decode(packet, responses, interfaceIndex, network);
-        if (errorCode == MdnsResponseDecoder.SUCCESS) {
-            if (responseType.equals(MULTICAST_TYPE)) {
-                receivedMulticastResponse = true;
-                if (cannotReceiveMulticastResponse.getAndSet(false)) {
-                    // If we are already in the bad state, receiving a multicast response means
-                    // we are recovered.
-                    LOGGER.e(
-                            "Recovered from the state where the phone can't receive any multicast"
-                                    + " response");
-                }
-            } else {
-                receivedUnicastResponse = true;
-            }
-            for (MdnsResponse response : responses) {
-                String serviceInstanceName = response.getServiceInstanceName();
-                LOGGER.log("mDNS %s response received: %s at ifIndex %d", responseType,
-                        serviceInstanceName, interfaceIndex);
-                if (callback != null) {
-                    callback.onResponseReceived(response);
-                }
-            }
-        } else if (errorCode != MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE) {
+        final MdnsPacket response;
+        try {
+            response = MdnsResponseDecoder.parseResponse(packet.getData(), packet.getLength());
+        } catch (MdnsPacket.ParseException e) {
             LOGGER.w(String.format("Error while decoding %s packet (%d): %d",
-                    responseType, packetNumber, errorCode));
+                    responseType, packetNumber, e.code));
             if (callback != null) {
-                callback.onFailedToParseMdnsResponse(packetNumber, errorCode);
+                callback.onFailedToParseMdnsResponse(packetNumber, e.code, network);
             }
+            return e.code;
         }
-        return errorCode;
+
+        if (response == null) {
+            return MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE;
+        }
+
+        if (callback != null) {
+            callback.onResponseReceived(response, interfaceIndex, network);
+        }
+
+        return MdnsResponseErrorCode.SUCCESS;
     }
 
     @VisibleForTesting
@@ -512,11 +532,4 @@
     public boolean isOnIPv6OnlyNetwork() {
         return multicastSocket != null && multicastSocket.isOnIPv6OnlyNetwork();
     }
-
-    /** Callback for {@link MdnsSocketClient}. */
-    public interface Callback {
-        void onResponseReceived(@NonNull MdnsResponse response);
-
-        void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode);
-    }
 }
\ No newline at end of file
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java
new file mode 100644
index 0000000..deadc58
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClientBase.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Network;
+import android.os.Looper;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+
+/**
+ * Base class for multicast socket client.
+ *
+ * @hide
+ */
+public interface MdnsSocketClientBase {
+    /*** Start mDns discovery on given network. */
+    default void startDiscovery() throws IOException { }
+
+    /*** Stop mDns discovery. */
+    default void stopDiscovery() { }
+
+    /*** Set callback for receiving mDns response */
+    void setCallback(@Nullable Callback callback);
+
+    /**
+     * Send a mDNS request packet via given network that asks for multicast response.
+     *
+     * <p>The socket client may use a null network to identify some or all interfaces, in which case
+     * passing null sends the packet to these.
+     */
+    void sendMulticastPacket(@NonNull DatagramPacket packet, @Nullable Network network);
+
+    /**
+     * Send a mDNS request packet via given network that asks for unicast response.
+     *
+     * <p>The socket client may use a null network to identify some or all interfaces, in which case
+     * passing null sends the packet to these.
+     */
+    void sendUnicastPacket(@NonNull DatagramPacket packet, @Nullable Network network);
+
+    /*** Notify that the given network is requested for mdns discovery / resolution */
+    void notifyNetworkRequested(@NonNull MdnsServiceBrowserListener listener,
+            @Nullable Network network, @NonNull SocketCreationCallback socketCreationCallback);
+
+    /*** Notify that the network is unrequested */
+    default void notifyNetworkUnrequested(@NonNull MdnsServiceBrowserListener listener) { }
+
+    /*** Gets looper that used by the socket client */
+    default Looper getLooper() {
+        return null;
+    }
+
+    /*** Callback for mdns response  */
+    interface Callback {
+        /*** Receive a mdns response */
+        void onResponseReceived(@NonNull MdnsPacket packet, int interfaceIndex,
+                @Nullable Network network);
+
+        /*** Parse a mdns response failed */
+        void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode,
+                @Nullable Network network);
+    }
+
+    /*** Callback for requested socket creation  */
+    interface SocketCreationCallback {
+        /*** Notify requested socket is created */
+        void onSocketCreated(@Nullable Network network);
+
+        /*** Notify requested socket is destroyed */
+        void onAllSocketsDestroyed(@Nullable Network network);
+    }
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
new file mode 100644
index 0000000..2fa1ae4
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketProvider.java
@@ -0,0 +1,671 @@
+/*
+ * 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 com.android.server.connectivity.mdns;
+
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
+import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
+import static com.android.server.connectivity.mdns.util.MdnsUtils.isNetworkMatched;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.TetheringManager;
+import android.net.TetheringManager.TetheringEventCallback;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
+import com.android.net.module.util.SharedLog;
+
+import java.io.IOException;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The {@link MdnsSocketProvider} manages the multiple sockets for mDns.
+ *
+ * <p>This class is not thread safe, it is intended to be used only from the looper thread.
+ * However, the constructor is an exception, as it is called on another thread;
+ * therefore for thread safety all members of this class MUST either be final or initialized
+ * to their default value (0, false or null).
+ *
+ */
+public class MdnsSocketProvider {
+    private static final String TAG = MdnsSocketProvider.class.getSimpleName();
+    private static final boolean DBG = MdnsDiscoveryManager.DBG;
+    // This buffer size matches what MdnsSocketClient uses currently.
+    // But 1440 should generally be enough because of standard Ethernet.
+    // Note: mdnsresponder mDNSEmbeddedAPI.h uses 8940 for Ethernet jumbo frames.
+    private static final int READ_BUFFER_SIZE = 2048;
+    private static final int IFACE_IDX_NOT_EXIST = -1;
+    @NonNull private final Context mContext;
+    @NonNull private final Looper mLooper;
+    @NonNull private final Handler mHandler;
+    @NonNull private final Dependencies mDependencies;
+    @NonNull private final NetworkCallback mNetworkCallback;
+    @NonNull private final TetheringEventCallback mTetheringEventCallback;
+    @NonNull private final AbstractSocketNetlink mSocketNetlinkMonitor;
+    @NonNull private final SharedLog mSharedLog;
+    private final ArrayMap<Network, SocketInfo> mNetworkSockets = new ArrayMap<>();
+    private final ArrayMap<String, SocketInfo> mTetherInterfaceSockets = new ArrayMap<>();
+    private final ArrayMap<Network, LinkProperties> mActiveNetworksLinkProperties =
+            new ArrayMap<>();
+    private final ArrayMap<Network, int[]> mActiveNetworksTransports = new ArrayMap<>();
+    private final ArrayMap<SocketCallback, Network> mCallbacksToRequestedNetworks =
+            new ArrayMap<>();
+    private final List<String> mLocalOnlyInterfaces = new ArrayList<>();
+    private final List<String> mTetheredInterfaces = new ArrayList<>();
+    // mIfaceIdxToLinkProperties should not be cleared in maybeStopMonitoringSockets() because
+    // the netlink monitor is never stop and the old states must be kept.
+    private final SparseArray<LinkProperties> mIfaceIdxToLinkProperties = new SparseArray<>();
+    private final byte[] mPacketReadBuffer = new byte[READ_BUFFER_SIZE];
+    private boolean mMonitoringSockets = false;
+    private boolean mRequestStop = false;
+
+    public MdnsSocketProvider(@NonNull Context context, @NonNull Looper looper,
+            @NonNull SharedLog sharedLog) {
+        this(context, looper, new Dependencies(), sharedLog);
+    }
+
+    MdnsSocketProvider(@NonNull Context context, @NonNull Looper looper,
+            @NonNull Dependencies deps, @NonNull SharedLog sharedLog) {
+        mContext = context;
+        mLooper = looper;
+        mHandler = new Handler(looper);
+        mDependencies = deps;
+        mSharedLog = sharedLog;
+        mNetworkCallback = new NetworkCallback() {
+            @Override
+            public void onLost(Network network) {
+                mActiveNetworksLinkProperties.remove(network);
+                mActiveNetworksTransports.remove(network);
+                removeNetworkSocket(network);
+            }
+
+            @Override
+            public void onCapabilitiesChanged(@NonNull Network network,
+                    @NonNull NetworkCapabilities networkCapabilities) {
+                mActiveNetworksTransports.put(network, networkCapabilities.getTransportTypes());
+            }
+
+            @Override
+            public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
+                handleLinkPropertiesChanged(network, lp);
+            }
+        };
+        mTetheringEventCallback = new TetheringEventCallback() {
+            @Override
+            public void onLocalOnlyInterfacesChanged(@NonNull List<String> interfaces) {
+                handleTetherInterfacesChanged(mLocalOnlyInterfaces, interfaces);
+            }
+
+            @Override
+            public void onTetheredInterfacesChanged(@NonNull List<String> interfaces) {
+                handleTetherInterfacesChanged(mTetheredInterfaces, interfaces);
+            }
+        };
+
+        mSocketNetlinkMonitor = mDependencies.createSocketNetlinkMonitor(mHandler,
+                mSharedLog.forSubComponent("NetlinkMonitor"), new NetLinkMessageProcessor());
+    }
+
+    /**
+     * Dependencies of MdnsSocketProvider, for injection in tests.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        /*** Get network interface by given interface name */
+        public NetworkInterfaceWrapper getNetworkInterfaceByName(@NonNull String interfaceName)
+                throws SocketException {
+            final NetworkInterface ni = NetworkInterface.getByName(interfaceName);
+            return ni == null ? null : new NetworkInterfaceWrapper(ni);
+        }
+
+        /*** Create a MdnsInterfaceSocket */
+        public MdnsInterfaceSocket createMdnsInterfaceSocket(
+                @NonNull NetworkInterface networkInterface, int port, @NonNull Looper looper,
+                @NonNull byte[] packetReadBuffer) throws IOException {
+            return new MdnsInterfaceSocket(networkInterface, port, looper, packetReadBuffer);
+        }
+
+        /*** Get network interface by given interface name */
+        public int getNetworkInterfaceIndexByName(@NonNull final String ifaceName) {
+            final NetworkInterface iface;
+            try {
+                iface = NetworkInterface.getByName(ifaceName);
+            } catch (SocketException e) {
+                Log.e(TAG, "Error querying interface", e);
+                return IFACE_IDX_NOT_EXIST;
+            }
+            if (iface == null) {
+                Log.e(TAG, "Interface not found: " + ifaceName);
+                return IFACE_IDX_NOT_EXIST;
+            }
+            return iface.getIndex();
+        }
+        /*** Creates a SocketNetlinkMonitor */
+        public AbstractSocketNetlink createSocketNetlinkMonitor(@NonNull final Handler handler,
+                @NonNull final SharedLog log,
+                @NonNull final NetLinkMonitorCallBack cb) {
+            return SocketNetLinkMonitorFactory.createNetLinkMonitor(handler, log, cb);
+        }
+    }
+    /**
+     * The callback interface for the netlink monitor messages.
+     */
+    public interface NetLinkMonitorCallBack {
+        /**
+         * Handles the interface address add or update.
+         */
+        void addOrUpdateInterfaceAddress(int ifaceIdx, @NonNull LinkAddress newAddress);
+
+
+        /**
+         * Handles the interface address delete.
+         */
+        void deleteInterfaceAddress(int ifaceIdx, @NonNull LinkAddress deleteAddress);
+    }
+    private class NetLinkMessageProcessor implements NetLinkMonitorCallBack {
+
+        @Override
+        public void addOrUpdateInterfaceAddress(int ifaceIdx,
+                @NonNull final LinkAddress newAddress) {
+
+            LinkProperties linkProperties;
+            linkProperties = mIfaceIdxToLinkProperties.get(ifaceIdx);
+            if (linkProperties == null) {
+                linkProperties = new LinkProperties();
+                mIfaceIdxToLinkProperties.put(ifaceIdx, linkProperties);
+            }
+            boolean updated = linkProperties.addLinkAddress(newAddress);
+
+            if (!updated) {
+                return;
+            }
+            maybeUpdateTetheringSocketAddress(ifaceIdx, linkProperties.getLinkAddresses());
+        }
+
+        @Override
+        public void deleteInterfaceAddress(int ifaceIdx, @NonNull LinkAddress deleteAddress) {
+            LinkProperties linkProperties;
+            boolean updated = false;
+            linkProperties = mIfaceIdxToLinkProperties.get(ifaceIdx);
+            if (linkProperties != null) {
+                updated = linkProperties.removeLinkAddress(deleteAddress);
+                if (linkProperties.getLinkAddresses().isEmpty()) {
+                    mIfaceIdxToLinkProperties.remove(ifaceIdx);
+                }
+            }
+
+            if (linkProperties == null || !updated) {
+                return;
+            }
+            maybeUpdateTetheringSocketAddress(ifaceIdx, linkProperties.getLinkAddresses());
+
+        }
+    }
+    /*** Data class for storing socket related info  */
+    private static class SocketInfo {
+        final MdnsInterfaceSocket mSocket;
+        final List<LinkAddress> mAddresses;
+
+        SocketInfo(MdnsInterfaceSocket socket, List<LinkAddress> addresses) {
+            mSocket = socket;
+            mAddresses = new ArrayList<>(addresses);
+        }
+    }
+
+    /*** Start monitoring sockets by listening callbacks for sockets creation or removal */
+    public void startMonitoringSockets() {
+        ensureRunningOnHandlerThread(mHandler);
+        mRequestStop = false; // Reset stop request flag.
+        if (mMonitoringSockets) {
+            Log.d(TAG, "Already monitoring sockets.");
+            return;
+        }
+        mSharedLog.i("Start monitoring sockets.");
+        mContext.getSystemService(ConnectivityManager.class).registerNetworkCallback(
+                new NetworkRequest.Builder().clearCapabilities().build(),
+                mNetworkCallback, mHandler);
+
+        final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class);
+        tetheringManager.registerTetheringEventCallback(mHandler::post, mTetheringEventCallback);
+
+        if (mSocketNetlinkMonitor.isSupported()) {
+            mHandler.post(mSocketNetlinkMonitor::startMonitoring);
+        }
+        mMonitoringSockets = true;
+    }
+    /**
+     * Start netlink monitor.
+     */
+    public void startNetLinkMonitor() {
+        ensureRunningOnHandlerThread(mHandler);
+        if (mSocketNetlinkMonitor.isSupported()) {
+            mSocketNetlinkMonitor.startMonitoring();
+        }
+    }
+
+    private void maybeStopMonitoringSockets() {
+        if (!mMonitoringSockets) return; // Already unregistered.
+        if (!mRequestStop) return; // No stop request.
+
+        // Only unregister the network callback if there is no socket request.
+        if (mCallbacksToRequestedNetworks.isEmpty()) {
+            mSharedLog.i("Stop monitoring sockets.");
+            mContext.getSystemService(ConnectivityManager.class)
+                    .unregisterNetworkCallback(mNetworkCallback);
+
+            final TetheringManager tetheringManager = mContext.getSystemService(
+                    TetheringManager.class);
+            tetheringManager.unregisterTetheringEventCallback(mTetheringEventCallback);
+            // Clear all saved status.
+            mActiveNetworksLinkProperties.clear();
+            mNetworkSockets.clear();
+            mTetherInterfaceSockets.clear();
+            mLocalOnlyInterfaces.clear();
+            mTetheredInterfaces.clear();
+            mMonitoringSockets = false;
+        }
+        // The netlink monitor is not stopped here because the MdnsSocketProvider need to listen
+        // to all the netlink updates when the system is up and running.
+    }
+
+    /*** Request to stop monitoring sockets and unregister callbacks */
+    public void requestStopWhenInactive() {
+        ensureRunningOnHandlerThread(mHandler);
+        if (!mMonitoringSockets) {
+            Log.d(TAG, "Monitoring sockets hasn't been started.");
+            return;
+        }
+        mRequestStop = true;
+        maybeStopMonitoringSockets();
+    }
+
+    private boolean matchRequestedNetwork(Network network) {
+        return hasAllNetworksRequest()
+                || mCallbacksToRequestedNetworks.containsValue(network);
+    }
+
+    private boolean hasAllNetworksRequest() {
+        return mCallbacksToRequestedNetworks.containsValue(null);
+    }
+
+    private void handleLinkPropertiesChanged(Network network, LinkProperties lp) {
+        mActiveNetworksLinkProperties.put(network, lp);
+        if (!matchRequestedNetwork(network)) {
+            if (DBG) {
+                Log.d(TAG, "Ignore LinkProperties change. There is no request for the"
+                        + " Network:" + network);
+            }
+            return;
+        }
+
+        final NetworkAsKey networkKey = new NetworkAsKey(network);
+        final SocketInfo socketInfo = mNetworkSockets.get(network);
+        if (socketInfo == null) {
+            createSocket(networkKey, lp);
+        } else {
+            updateSocketInfoAddress(network, socketInfo, lp.getLinkAddresses());
+        }
+    }
+    private void maybeUpdateTetheringSocketAddress(int ifaceIndex,
+            @NonNull final List<LinkAddress> updatedAddresses) {
+        for (int i = 0; i < mTetherInterfaceSockets.size(); ++i) {
+            String tetheringInterfaceName = mTetherInterfaceSockets.keyAt(i);
+            if (mDependencies.getNetworkInterfaceIndexByName(tetheringInterfaceName)
+                    == ifaceIndex) {
+                updateSocketInfoAddress(null /* network */,
+                        mTetherInterfaceSockets.valueAt(i), updatedAddresses);
+                return;
+            }
+        }
+    }
+
+    private void updateSocketInfoAddress(@Nullable final Network network,
+            @NonNull final SocketInfo socketInfo,
+            @NonNull final List<LinkAddress> addresses) {
+        // Update the addresses of this socket.
+        socketInfo.mAddresses.clear();
+        socketInfo.mAddresses.addAll(addresses);
+        // Try to join the group again.
+        socketInfo.mSocket.joinGroup(addresses);
+
+        notifyAddressesChanged(network, socketInfo.mSocket, addresses);
+    }
+    private LinkProperties createLPForTetheredInterface(@NonNull final String interfaceName,
+            int ifaceIndex) {
+        final LinkProperties linkProperties =
+                new LinkProperties(mIfaceIdxToLinkProperties.get(ifaceIndex));
+        linkProperties.setInterfaceName(interfaceName);
+        return linkProperties;
+    }
+
+    private void handleTetherInterfacesChanged(List<String> current, List<String> updated) {
+        if (!hasAllNetworksRequest()) {
+            // Currently, the network for tethering can not be requested, so the sockets for
+            // tethering are only created if there is a request for all networks (interfaces).
+            // Therefore, this change can skip if there is no such request.
+            if (DBG) {
+                Log.d(TAG, "Ignore tether interfaces change. There is no request for all"
+                        + " networks.");
+            }
+            return;
+        }
+
+        final CompareResult<String> interfaceDiff = new CompareResult<>(
+                current, updated);
+        for (String name : interfaceDiff.added) {
+            int ifaceIndex = mDependencies.getNetworkInterfaceIndexByName(name);
+            createSocket(LOCAL_NET, createLPForTetheredInterface(name, ifaceIndex));
+        }
+        for (String name : interfaceDiff.removed) {
+            removeTetherInterfaceSocket(name);
+        }
+        current.clear();
+        current.addAll(updated);
+    }
+
+    private void createSocket(NetworkKey networkKey, LinkProperties lp) {
+        final String interfaceName = lp.getInterfaceName();
+        if (interfaceName == null) {
+            Log.e(TAG, "Can not create socket with null interface name.");
+            return;
+        }
+
+        try {
+            final NetworkInterfaceWrapper networkInterface =
+                    mDependencies.getNetworkInterfaceByName(interfaceName);
+            // There are no transports for tethered interfaces. Other interfaces should always
+            // have transports since LinkProperties updates are always sent after
+            // NetworkCapabilities updates.
+            final int[] transports;
+            if (networkKey == LOCAL_NET) {
+                transports = new int[0];
+            } else {
+                transports = mActiveNetworksTransports.get(((NetworkAsKey) networkKey).mNetwork);
+                if (transports == null) {
+                    Log.wtf(TAG, "transports is missing for key: " + networkKey);
+                }
+            }
+            if (networkInterface == null || !isMdnsCapableInterface(networkInterface, transports)) {
+                return;
+            }
+
+            mSharedLog.log("Create socket on net:" + networkKey + ", ifName:" + interfaceName);
+            final MdnsInterfaceSocket socket = mDependencies.createMdnsInterfaceSocket(
+                    networkInterface.getNetworkInterface(), MdnsConstants.MDNS_PORT, mLooper,
+                    mPacketReadBuffer);
+            final List<LinkAddress> addresses = lp.getLinkAddresses();
+            if (networkKey == LOCAL_NET) {
+                mTetherInterfaceSockets.put(interfaceName, new SocketInfo(socket, addresses));
+            } else {
+                mNetworkSockets.put(((NetworkAsKey) networkKey).mNetwork,
+                        new SocketInfo(socket, addresses));
+            }
+            // Try to join IPv4/IPv6 group.
+            socket.joinGroup(addresses);
+
+            // Notify the listeners which need this socket.
+            if (networkKey == LOCAL_NET) {
+                notifySocketCreated(null /* network */, socket, addresses);
+            } else {
+                notifySocketCreated(((NetworkAsKey) networkKey).mNetwork, socket, addresses);
+            }
+        } catch (IOException e) {
+            mSharedLog.e("Create socket failed ifName:" + interfaceName, e);
+        }
+    }
+
+    private boolean isMdnsCapableInterface(
+            @NonNull NetworkInterfaceWrapper iface, @NonNull int[] transports) {
+        try {
+            // Never try mDNS on cellular, or on interfaces with incompatible flags
+            if (CollectionUtils.contains(transports, TRANSPORT_CELLULAR)
+                    || iface.isLoopback()
+                    || iface.isPointToPoint()
+                    || iface.isVirtual()
+                    || !iface.isUp()) {
+                return false;
+            }
+
+            // Otherwise, always try mDNS on non-VPN Wifi.
+            if (!CollectionUtils.contains(transports, TRANSPORT_VPN)
+                    && CollectionUtils.contains(transports, TRANSPORT_WIFI)) {
+                return true;
+            }
+
+            // For other transports, or no transports (tethering downstreams), do mDNS based on the
+            // interface flags. This is not always reliable (for example some Wifi interfaces may
+            // not have the MULTICAST flag even though they can do mDNS, and some cellular
+            // interfaces may have the BROADCAST or MULTICAST flags), so checks are done based on
+            // transports above in priority.
+            return iface.supportsMulticast();
+        } catch (SocketException e) {
+            mSharedLog.e("Error checking interface flags", e);
+            return false;
+        }
+    }
+
+    private void removeNetworkSocket(Network network) {
+        final SocketInfo socketInfo = mNetworkSockets.remove(network);
+        if (socketInfo == null) return;
+
+        socketInfo.mSocket.destroy();
+        notifyInterfaceDestroyed(network, socketInfo.mSocket);
+        mSharedLog.log("Remove socket on net:" + network);
+    }
+
+    private void removeTetherInterfaceSocket(String interfaceName) {
+        final SocketInfo socketInfo = mTetherInterfaceSockets.remove(interfaceName);
+        if (socketInfo == null) return;
+        socketInfo.mSocket.destroy();
+        notifyInterfaceDestroyed(null /* network */, socketInfo.mSocket);
+        mSharedLog.log("Remove socket on ifName:" + interfaceName);
+    }
+
+    private void notifySocketCreated(Network network, MdnsInterfaceSocket socket,
+            List<LinkAddress> addresses) {
+        for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) {
+            final Network requestedNetwork = mCallbacksToRequestedNetworks.valueAt(i);
+            if (isNetworkMatched(requestedNetwork, network)) {
+                mCallbacksToRequestedNetworks.keyAt(i).onSocketCreated(network, socket, addresses);
+            }
+        }
+    }
+
+    private void notifyInterfaceDestroyed(Network network, MdnsInterfaceSocket socket) {
+        for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) {
+            final Network requestedNetwork = mCallbacksToRequestedNetworks.valueAt(i);
+            if (isNetworkMatched(requestedNetwork, network)) {
+                mCallbacksToRequestedNetworks.keyAt(i).onInterfaceDestroyed(network, socket);
+            }
+        }
+    }
+
+    private void notifyAddressesChanged(Network network, MdnsInterfaceSocket socket,
+            List<LinkAddress> addresses) {
+        for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) {
+            final Network requestedNetwork = mCallbacksToRequestedNetworks.valueAt(i);
+            if (isNetworkMatched(requestedNetwork, network)) {
+                mCallbacksToRequestedNetworks.keyAt(i)
+                        .onAddressesChanged(network, socket, addresses);
+            }
+        }
+    }
+
+    private void retrieveAndNotifySocketFromNetwork(Network network, SocketCallback cb) {
+        final SocketInfo socketInfo = mNetworkSockets.get(network);
+        if (socketInfo == null) {
+            final LinkProperties lp = mActiveNetworksLinkProperties.get(network);
+            if (lp == null) {
+                // The requested network is not existed. Maybe wait for LinkProperties change later.
+                if (DBG) Log.d(TAG, "There is no LinkProperties for this network:" + network);
+                return;
+            }
+            createSocket(new NetworkAsKey(network), lp);
+        } else {
+            // Notify the socket for requested network.
+            cb.onSocketCreated(network, socketInfo.mSocket, socketInfo.mAddresses);
+        }
+    }
+
+    private void retrieveAndNotifySocketFromInterface(String interfaceName, SocketCallback cb) {
+        final SocketInfo socketInfo = mTetherInterfaceSockets.get(interfaceName);
+        if (socketInfo == null) {
+            int ifaceIndex = mDependencies.getNetworkInterfaceIndexByName(interfaceName);
+            createSocket(
+                    LOCAL_NET,
+                    createLPForTetheredInterface(interfaceName, ifaceIndex));
+        } else {
+            // Notify the socket for requested network.
+            cb.onSocketCreated(
+                    null /* network */, socketInfo.mSocket, socketInfo.mAddresses);
+        }
+    }
+
+    /**
+     * Request a socket for given network.
+     *
+     * @param network the required network for a socket. Null means create sockets on all possible
+     *                networks (interfaces).
+     * @param cb the callback to listen the socket creation.
+     */
+    public void requestSocket(@Nullable Network network, @NonNull SocketCallback cb) {
+        ensureRunningOnHandlerThread(mHandler);
+        mSharedLog.log("requestSocket for net:" + network);
+        mCallbacksToRequestedNetworks.put(cb, network);
+        if (network == null) {
+            // Does not specify a required network, create sockets for all possible
+            // networks (interfaces).
+            for (int i = 0; i < mActiveNetworksLinkProperties.size(); i++) {
+                retrieveAndNotifySocketFromNetwork(mActiveNetworksLinkProperties.keyAt(i), cb);
+            }
+
+            for (String localInterface : mLocalOnlyInterfaces) {
+                retrieveAndNotifySocketFromInterface(localInterface, cb);
+            }
+
+            for (String tetheredInterface : mTetheredInterfaces) {
+                retrieveAndNotifySocketFromInterface(tetheredInterface, cb);
+            }
+        } else {
+            retrieveAndNotifySocketFromNetwork(network, cb);
+        }
+    }
+
+    /*** Unrequest the socket */
+    public void unrequestSocket(@NonNull SocketCallback cb) {
+        ensureRunningOnHandlerThread(mHandler);
+        mSharedLog.log("unrequestSocket");
+        mCallbacksToRequestedNetworks.remove(cb);
+        if (hasAllNetworksRequest()) {
+            // Still has a request for all networks (interfaces).
+            return;
+        }
+
+        // Check if remaining requests are matched any of sockets.
+        for (int i = mNetworkSockets.size() - 1; i >= 0; i--) {
+            final Network network = mNetworkSockets.keyAt(i);
+            if (matchRequestedNetwork(network)) continue;
+            final SocketInfo info = mNetworkSockets.removeAt(i);
+            info.mSocket.destroy();
+            mSharedLog.log("Remove socket on net:" + network + " after unrequestSocket");
+        }
+
+        // Remove all sockets for tethering interface because these sockets do not have associated
+        // networks, and they should invoke by a request for all networks (interfaces). If there is
+        // no such request, the sockets for tethering interface should be removed.
+        for (int i = mTetherInterfaceSockets.size() - 1; i >= 0; i--) {
+            final SocketInfo info = mTetherInterfaceSockets.valueAt(i);
+            info.mSocket.destroy();
+            mSharedLog.log("Remove socket on ifName:" + mTetherInterfaceSockets.keyAt(i)
+                    + " after unrequestSocket");
+        }
+        mTetherInterfaceSockets.clear();
+
+        // Try to unregister network callback.
+        maybeStopMonitoringSockets();
+    }
+
+
+    /*** Callbacks for listening socket changes */
+    public interface SocketCallback {
+        /*** Notify the socket is created */
+        default void onSocketCreated(@Nullable Network network, @NonNull MdnsInterfaceSocket socket,
+                @NonNull List<LinkAddress> addresses) {}
+        /*** Notify the interface is destroyed */
+        default void onInterfaceDestroyed(@Nullable Network network,
+                @NonNull MdnsInterfaceSocket socket) {}
+        /*** Notify the addresses is changed on the network */
+        default void onAddressesChanged(@Nullable Network network,
+                @NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> addresses) {}
+    }
+
+    private interface NetworkKey {
+    }
+
+    private static final NetworkKey LOCAL_NET = new NetworkKey() {
+        @Override
+        public String toString() {
+            return "NetworkKey:LOCAL_NET";
+        }
+    };
+
+    private static class NetworkAsKey implements NetworkKey {
+        private final Network mNetwork;
+
+        NetworkAsKey(Network network) {
+            this.mNetwork = network;
+        }
+
+        @Override
+        public int hashCode() {
+            return mNetwork.hashCode();
+        }
+
+        @Override
+        public boolean equals(@Nullable Object other) {
+            if (!(other instanceof NetworkAsKey)) {
+                return false;
+            }
+            return mNetwork.equals(((NetworkAsKey) other).mNetwork);
+        }
+
+        @Override
+        public String toString() {
+            return "NetworkAsKey{ network=" + mNetwork + " }";
+        }
+    }
+}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsTextRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsTextRecord.java
similarity index 100%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsTextRecord.java
rename to service-t/src/com/android/server/connectivity/mdns/MdnsTextRecord.java
diff --git a/service/mdns/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java b/service-t/src/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java
similarity index 98%
rename from service/mdns/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java
rename to service-t/src/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java
index ade7b95..f248c98 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java
@@ -148,7 +148,7 @@
     }
 
     /*** Check whether given network interface can support mdns */
-    public static boolean canScanOnInterface(@Nullable NetworkInterfaceWrapper networkInterface) {
+    private static boolean canScanOnInterface(@Nullable NetworkInterfaceWrapper networkInterface) {
         try {
             if ((networkInterface == null)
                     || networkInterface.isLoopback()
diff --git a/service-t/src/com/android/server/connectivity/mdns/MulticastPacketReader.java b/service-t/src/com/android/server/connectivity/mdns/MulticastPacketReader.java
new file mode 100644
index 0000000..63119ac
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MulticastPacketReader.java
@@ -0,0 +1,123 @@
+/*
+ * 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 com.android.server.connectivity.mdns;
+
+import static com.android.server.connectivity.mdns.util.MdnsUtils.ensureRunningOnHandlerThread;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.ParcelFileDescriptor;
+import android.system.Os;
+import android.util.ArraySet;
+
+import com.android.net.module.util.FdEventsReader;
+
+import java.io.FileDescriptor;
+import java.net.InetSocketAddress;
+import java.util.Set;
+
+/** Simple reader for mDNS packets. */
+public class MulticastPacketReader extends FdEventsReader<MulticastPacketReader.RecvBuffer> {
+    @NonNull
+    private final String mLogTag;
+    @NonNull
+    private final ParcelFileDescriptor mSocket;
+    @NonNull
+    private final Handler mHandler;
+    @NonNull
+    private final Set<PacketHandler> mPacketHandlers = new ArraySet<>();
+
+    interface PacketHandler {
+        void handlePacket(byte[] recvbuf, int length, InetSocketAddress src);
+    }
+
+    public static final class RecvBuffer {
+        final byte[] data;
+        final InetSocketAddress src;
+
+        private RecvBuffer(byte[] data, InetSocketAddress src) {
+            this.data = data;
+            this.src = src;
+        }
+    }
+
+    /**
+     * Create a new {@link MulticastPacketReader}.
+     * @param socket Socket to read from. This will *not* be closed when the reader terminates.
+     * @param buffer Buffer to read packets into. Will only be used from the handler thread.
+     * @param port the port number for the socket
+     */
+    protected MulticastPacketReader(@NonNull String interfaceTag,
+            @NonNull ParcelFileDescriptor socket, @NonNull Handler handler,
+            @NonNull byte[] buffer) {
+        // Set the port to zero as placeholder as the recvfrom() call will fill the actual port
+        // value later.
+        super(handler, new RecvBuffer(buffer, new InetSocketAddress(0 /* port */)));
+        mLogTag = MulticastPacketReader.class.getSimpleName() + "/" + interfaceTag;
+        mSocket = socket;
+        mHandler = handler;
+    }
+
+    @Override
+    protected int recvBufSize(@NonNull RecvBuffer buffer) {
+        return buffer.data.length;
+    }
+
+    @Override
+    protected FileDescriptor createFd() {
+        // Keep a reference to the PFD as it would close the fd in its finalizer otherwise
+        return mSocket.getFileDescriptor();
+    }
+
+    @Override
+    protected void onStop() {
+        // Do nothing (do not close the FD)
+    }
+
+    @Override
+    protected int readPacket(@NonNull FileDescriptor fd, @NonNull RecvBuffer buffer)
+            throws Exception {
+        return Os.recvfrom(
+                fd, buffer.data, 0, buffer.data.length, 0 /* flags */, buffer.src);
+    }
+
+    @Override
+    protected void handlePacket(@NonNull RecvBuffer recvbuf, int length) {
+        for (PacketHandler handler : mPacketHandlers) {
+            handler.handlePacket(recvbuf.data, length, recvbuf.src);
+        }
+    }
+
+    /**
+     * Add a packet handler to deal with received packets. If the handler is already set,
+     * this is a no-op.
+     */
+    public void addPacketHandler(@NonNull PacketHandler handler) {
+        ensureRunningOnHandlerThread(mHandler);
+        mPacketHandlers.add(handler);
+    }
+
+    /**
+     * Remove a packet handler added via {@link #addPacketHandler}. If the handler was not set,
+     * this is a no-op.
+     */
+    public void removePacketHandler(@NonNull PacketHandler handler) {
+        ensureRunningOnHandlerThread(mHandler);
+        mPacketHandlers.remove(handler);
+    }
+}
+
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/NameConflictException.java
similarity index 64%
rename from service/mdns/com/android/server/connectivity/mdns/MdnsAdvertiser.java
rename to service-t/src/com/android/server/connectivity/mdns/NameConflictException.java
index dee78fd..c123d02 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/NameConflictException.java
@@ -16,14 +16,15 @@
 
 package com.android.server.connectivity.mdns;
 
-import android.util.Log;
-
 /**
- * MdnsAdvertiser manages advertising services per {@link com.android.server.NsdService} requests.
- *
- * TODO: implement
+ * An exception thrown when a service name conflicts with an existing service.
  */
-public class MdnsAdvertiser {
-    private static final String TAG = MdnsAdvertiser.class.getSimpleName();
-    public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+public class NameConflictException extends Exception {
+    /**
+     * ID of the existing service that conflicted.
+     */
+    public final int conflictingServiceId;
+    public NameConflictException(int conflictingServiceId) {
+        this.conflictingServiceId = conflictingServiceId;
+    }
 }
diff --git a/service/mdns/com/android/server/connectivity/mdns/NetworkInterfaceWrapper.java b/service-t/src/com/android/server/connectivity/mdns/NetworkInterfaceWrapper.java
similarity index 100%
rename from service/mdns/com/android/server/connectivity/mdns/NetworkInterfaceWrapper.java
rename to service-t/src/com/android/server/connectivity/mdns/NetworkInterfaceWrapper.java
diff --git a/service-t/src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java b/service-t/src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java
new file mode 100644
index 0000000..6bc7941
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/SocketNetLinkMonitorFactory.java
@@ -0,0 +1,41 @@
+/*
+ * 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 com.android.server.connectivity.mdns;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.mdns.internal.SocketNetlinkMonitor;
+
+/**
+ * The factory class for creating the netlink monitor.
+ */
+public class SocketNetLinkMonitorFactory {
+
+    /**
+     * Creates a new netlink monitor.
+     */
+    public static AbstractSocketNetlink createNetLinkMonitor(@NonNull final Handler handler,
+            @NonNull SharedLog log, @NonNull MdnsSocketProvider.NetLinkMonitorCallBack cb) {
+        return new SocketNetlinkMonitor(handler, log, cb);
+    }
+
+    private SocketNetLinkMonitorFactory() {
+    }
+
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java b/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java
new file mode 100644
index 0000000..451909c
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java
@@ -0,0 +1,92 @@
+/*
+ * 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 com.android.server.connectivity.mdns.internal;
+
+import android.annotation.NonNull;
+import android.net.LinkAddress;
+import android.os.Handler;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.ip.NetlinkMonitor;
+import com.android.net.module.util.netlink.NetlinkConstants;
+import com.android.net.module.util.netlink.NetlinkMessage;
+import com.android.net.module.util.netlink.RtNetlinkAddressMessage;
+import com.android.net.module.util.netlink.StructIfaddrMsg;
+import com.android.server.connectivity.mdns.AbstractSocketNetlink;
+import com.android.server.connectivity.mdns.MdnsSocketProvider;
+
+/**
+ * The netlink monitor for MdnsSocketProvider.
+ */
+public class SocketNetlinkMonitor extends NetlinkMonitor implements AbstractSocketNetlink {
+
+    public static final String TAG = SocketNetlinkMonitor.class.getSimpleName();
+
+    @NonNull
+    private final MdnsSocketProvider.NetLinkMonitorCallBack mCb;
+    public SocketNetlinkMonitor(@NonNull final Handler handler,
+            @NonNull SharedLog log,
+            @NonNull final MdnsSocketProvider.NetLinkMonitorCallBack cb) {
+        super(handler, log, TAG, OsConstants.NETLINK_ROUTE,
+                NetlinkConstants.RTMGRP_IPV4_IFADDR | NetlinkConstants.RTMGRP_IPV6_IFADDR);
+        mCb = cb;
+    }
+    @Override
+    public void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) {
+        if (nlMsg instanceof RtNetlinkAddressMessage) {
+            processRtNetlinkAddressMessage((RtNetlinkAddressMessage) nlMsg);
+        }
+    }
+
+    /**
+     * Process the RTM_NEWADDR and RTM_DELADDR netlink message.
+     */
+    private void processRtNetlinkAddressMessage(RtNetlinkAddressMessage msg) {
+        final StructIfaddrMsg ifaddrMsg = msg.getIfaddrHeader();
+        final LinkAddress la = new LinkAddress(msg.getIpAddress(), ifaddrMsg.prefixLen,
+                msg.getFlags(), ifaddrMsg.scope);
+        switch (msg.getHeader().nlmsg_type) {
+            case NetlinkConstants.RTM_NEWADDR:
+                if (la.isPreferred()) {
+                    mCb.addOrUpdateInterfaceAddress(ifaddrMsg.index, la);
+                }
+                break;
+            case NetlinkConstants.RTM_DELADDR:
+                mCb.deleteInterfaceAddress(ifaddrMsg.index, la);
+                break;
+            default:
+                Log.e(TAG, "Unknown rtnetlink address msg type " + msg.getHeader().nlmsg_type);
+        }
+    }
+
+    @Override
+    public boolean isSupported() {
+        return true;
+    }
+
+    @Override
+    public void startMonitoring() {
+        this.start();
+    }
+
+    @Override
+    public void stopMonitoring() {
+        this.stop();
+    }
+}
diff --git a/service/mdns/com/android/server/connectivity/mdns/util/MdnsLogger.java b/service-t/src/com/android/server/connectivity/mdns/util/MdnsLogger.java
similarity index 100%
rename from service/mdns/com/android/server/connectivity/mdns/util/MdnsLogger.java
rename to service-t/src/com/android/server/connectivity/mdns/util/MdnsLogger.java
diff --git a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
new file mode 100644
index 0000000..bc94869
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Network;
+import android.os.Handler;
+
+import com.android.server.connectivity.mdns.MdnsConstants;
+import com.android.server.connectivity.mdns.MdnsRecord;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Mdns utility functions.
+ */
+public class MdnsUtils {
+
+    private MdnsUtils() { }
+
+    /**
+     * Convert the string to DNS case-insensitive lowercase
+     *
+     * Per rfc6762#page-46, accented characters are not defined to be automatically equivalent to
+     * their unaccented counterparts. So the "DNS lowercase" should be if character is A-Z then they
+     * transform into a-z. Otherwise, they are kept as-is.
+     */
+    public static String toDnsLowerCase(@NonNull String string) {
+        final char[] outChars = new char[string.length()];
+        for (int i = 0; i < string.length(); i++) {
+            outChars[i] = toDnsLowerCase(string.charAt(i));
+        }
+        return new String(outChars);
+    }
+
+    /**
+     * Convert the array of labels to DNS case-insensitive lowercase.
+     */
+    public static String[] toDnsLabelsLowerCase(@NonNull String[] labels) {
+        final String[] outStrings = new String[labels.length];
+        for (int i = 0; i < labels.length; ++i) {
+            outStrings[i] = toDnsLowerCase(labels[i]);
+        }
+        return outStrings;
+    }
+
+    /**
+     * Compare two strings by DNS case-insensitive lowercase.
+     */
+    public static boolean equalsIgnoreDnsCase(@NonNull String a, @NonNull String b) {
+        if (a.length() != b.length()) return false;
+        for (int i = 0; i < a.length(); i++) {
+            if (toDnsLowerCase(a.charAt(i)) != toDnsLowerCase(b.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Compare two set of DNS labels by DNS case-insensitive lowercase.
+     */
+    public static boolean equalsDnsLabelIgnoreDnsCase(@NonNull String[] a, @NonNull String[] b) {
+        if (a == b) {
+            return true;
+        }
+        int length = a.length;
+        if (b.length != length) {
+            return false;
+        }
+        for (int i = 0; i < length; i++) {
+            if (!equalsIgnoreDnsCase(a[i], b[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Compare labels a equals b or a is suffix of b.
+     *
+     * @param a the type or subtype.
+     * @param b the base type
+     */
+    public static boolean typeEqualsOrIsSubtype(@NonNull String[] a,
+            @NonNull String[] b) {
+        return MdnsUtils.equalsDnsLabelIgnoreDnsCase(a, b)
+                || ((b.length == (a.length + 2))
+                && MdnsUtils.equalsIgnoreDnsCase(b[1], MdnsConstants.SUBTYPE_LABEL)
+                && MdnsRecord.labelsAreSuffix(a, b));
+    }
+
+    private static char toDnsLowerCase(char a) {
+        return a >= 'A' && a <= 'Z' ? (char) (a + ('a' - 'A')) : a;
+    }
+
+    /*** Ensure that current running thread is same as given handler thread */
+    public static void ensureRunningOnHandlerThread(@NonNull Handler handler) {
+        if (!isRunningOnHandlerThread(handler)) {
+            throw new IllegalStateException(
+                    "Not running on Handler thread: " + Thread.currentThread().getName());
+        }
+    }
+
+    /*** Check that current running thread is same as given handler thread */
+    public static boolean isRunningOnHandlerThread(@NonNull Handler handler) {
+        if (handler.getLooper().getThread() == Thread.currentThread()) {
+            return true;
+        }
+        return false;
+    }
+
+    /*** Check whether the target network is matched current network */
+    public static boolean isNetworkMatched(@Nullable Network targetNetwork,
+            @Nullable Network currentNetwork) {
+        return targetNetwork == null || targetNetwork.equals(currentNetwork);
+    }
+
+    /**
+     * Truncate a service name to up to maxLength UTF-8 bytes.
+     */
+    public static String truncateServiceName(@NonNull String originalName, int maxLength) {
+        // UTF-8 is at most 4 bytes per character; return early in the common case where
+        // the name can't possibly be over the limit given its string length.
+        if (originalName.length() <= maxLength / 4) return originalName;
+
+        final Charset utf8 = StandardCharsets.UTF_8;
+        final CharsetEncoder encoder = utf8.newEncoder();
+        final ByteBuffer out = ByteBuffer.allocate(maxLength);
+        // encode will write as many characters as possible to the out buffer, and just
+        // return an overflow code if there were too many characters (no need to check the
+        // return code here, this method truncates the name on purpose).
+        encoder.encode(CharBuffer.wrap(originalName), out, true /* endOfInput */);
+        return new String(out.array(), 0, out.position(), utf8);
+    }
+}
\ No newline at end of file
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
index 0605abe..6776920 100644
--- a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.net.ConnectivityManager;
-import android.net.ConnectivityResources;
 import android.net.EthernetManager;
 import android.net.EthernetNetworkSpecifier;
 import android.net.IpConfiguration;
@@ -50,6 +49,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.net.module.util.InterfaceParams;
+import com.android.server.connectivity.ConnectivityResources;
 
 import java.io.FileDescriptor;
 import java.util.Objects;
@@ -469,9 +469,7 @@
             // TODO: Update this logic to only do a restart if required. Although a restart may
             //  be required due to the capabilities or ipConfiguration values, not all
             //  capabilities changes require a restart.
-            if (mIpClient != null) {
-                restart();
-            }
+            maybeRestart();
         }
 
         boolean isRestricted() {
@@ -491,10 +489,19 @@
             mDeps.makeIpClient(mContext, name, mIpClientCallback);
             mIpClientCallback.awaitIpClientStart();
 
+            if (mIpConfig.getProxySettings() == ProxySettings.STATIC
+                    || mIpConfig.getProxySettings() == ProxySettings.PAC) {
+                mIpClient.setHttpProxy(mIpConfig.getHttpProxy());
+            }
+
             if (sTcpBufferSizes == null) {
                 sTcpBufferSizes = mDeps.getTcpBufferSizesFromResource(mContext);
             }
-            provisionIpClient(mIpClient, mIpConfig, sTcpBufferSizes);
+            if (!TextUtils.isEmpty(sTcpBufferSizes)) {
+                mIpClient.setTcpBufferSizes(sTcpBufferSizes);
+            }
+
+            mIpClient.startProvisioning(createProvisioningConfiguration(mIpConfig));
         }
 
         void onIpLayerStarted(@NonNull final LinkProperties linkProperties) {
@@ -540,7 +547,7 @@
                 // Send a callback in case a provisioning request was in progress.
                 return;
             }
-            restart();
+            maybeRestart();
         }
 
         private void ensureRunningOnEthernetHandlerThread() {
@@ -573,7 +580,7 @@
             // If there is a better network, that will become default and apps
             // will be able to use internet. If ethernet gets connected again,
             // and has backhaul connectivity, it will become default.
-            restart();
+            maybeRestart();
         }
 
         /** Returns true if state has been modified */
@@ -635,20 +642,6 @@
             mRequestIds.clear();
         }
 
-        private static void provisionIpClient(@NonNull final IpClientManager ipClient,
-                @NonNull final IpConfiguration config, @NonNull final String tcpBufferSizes) {
-            if (config.getProxySettings() == ProxySettings.STATIC ||
-                    config.getProxySettings() == ProxySettings.PAC) {
-                ipClient.setHttpProxy(config.getHttpProxy());
-            }
-
-            if (!TextUtils.isEmpty(tcpBufferSizes)) {
-                ipClient.setTcpBufferSizes(tcpBufferSizes);
-            }
-
-            ipClient.startProvisioning(createProvisioningConfiguration(config));
-        }
-
         private static ProvisioningConfiguration createProvisioningConfiguration(
                 @NonNull final IpConfiguration config) {
             if (config.getIpAssignment() == IpAssignment.STATIC) {
@@ -661,8 +654,16 @@
                         .build();
         }
 
-        void restart() {
-            if (DBG) Log.d(TAG, "reconnecting Ethernet");
+        void maybeRestart() {
+            if (mIpClient == null) {
+                // If maybeRestart() is called from a provisioning failure, it is
+                // possible that link disappeared in the meantime. In that
+                // case, stop() has already been called and IpClient should not
+                // get restarted to prevent a provisioning failure loop.
+                Log.i(TAG, String.format("maybeRestart() called on stopped interface %s", name));
+                return;
+            }
+            if (DBG) Log.d(TAG, "restart IpClient");
             stop();
             start();
         }
diff --git a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
index f6a55c8..e7af569 100644
--- a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
+++ b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
@@ -92,7 +92,7 @@
     @Override
     public String[] getAvailableInterfaces() throws RemoteException {
         PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
-        return mTracker.getInterfaces(checkUseRestrictedNetworksPermission());
+        return mTracker.getClientModeInterfaces(checkUseRestrictedNetworksPermission());
     }
 
     /**
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 852cf42..1f22b02 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -25,7 +25,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.net.ConnectivityResources;
 import android.net.EthernetManager;
 import android.net.IEthernetServiceListener;
 import android.net.INetd;
@@ -57,6 +56,7 @@
 import com.android.net.module.util.netlink.NetlinkMessage;
 import com.android.net.module.util.netlink.RtNetlinkLinkMessage;
 import com.android.net.module.util.netlink.StructIfinfoMsg;
+import com.android.server.connectivity.ConnectivityResources;
 
 import java.io.FileDescriptor;
 import java.net.InetAddress;
@@ -385,7 +385,7 @@
         return mFactory.hasInterface(iface);
     }
 
-    String[] getInterfaces(boolean includeRestricted) {
+    String[] getClientModeInterfaces(boolean includeRestricted) {
         return mFactory.getAvailableInterfaces(includeRestricted);
     }
 
@@ -428,9 +428,12 @@
                 // Remote process has already died
                 return;
             }
-            for (String iface : getInterfaces(canUseRestrictedNetworks)) {
+            for (String iface : getClientModeInterfaces(canUseRestrictedNetworks)) {
                 unicastInterfaceStateChange(listener, iface);
             }
+            if (mTetheringInterfaceMode == INTERFACE_MODE_SERVER) {
+                unicastInterfaceStateChange(listener, mTetheringInterface);
+            }
 
             unicastEthernetStateChange(listener, mEthernetState);
         });
diff --git a/service-t/src/com/android/server/net/NetworkStatsFactory.java b/service-t/src/com/android/server/net/NetworkStatsFactory.java
index 8161f50..5f66f47 100644
--- a/service-t/src/com/android/server/net/NetworkStatsFactory.java
+++ b/service-t/src/com/android/server/net/NetworkStatsFactory.java
@@ -34,8 +34,6 @@
 
 import java.io.IOException;
 import java.net.ProtocolException;
-import java.util.Arrays;
-import java.util.HashSet;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -86,12 +84,9 @@
          * are expected to monotonically increase since device boot.
          */
         @NonNull
-        public NetworkStats getNetworkStatsDetail(int limitUid, @Nullable String[] limitIfaces,
-                int limitTag) throws IOException {
+        public NetworkStats getNetworkStatsDetail() throws IOException {
             final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
-            // TODO: remove both path and useBpfStats arguments.
-            // The path is never used if useBpfStats is true.
-            final int ret = nativeReadNetworkStatsDetail(stats, limitUid, limitIfaces, limitTag);
+            final int ret = nativeReadNetworkStatsDetail(stats);
             if (ret != 0) {
                 throw new IOException("Failed to parse network stats");
             }
@@ -147,36 +142,6 @@
     }
 
     /**
-     * Get a set of interfaces containing specified ifaces and stacked interfaces.
-     *
-     * <p>The added stacked interfaces are ifaces stacked on top of the specified ones, or ifaces
-     * on which the specified ones are stacked. Stacked interfaces are those noted with
-     * {@link #noteStackedIface(String, String)}, but only interfaces noted before this method
-     * is called are guaranteed to be included.
-     */
-    public String[] augmentWithStackedInterfaces(@Nullable String[] requiredIfaces) {
-        if (requiredIfaces == NetworkStats.INTERFACES_ALL) {
-            return null;
-        }
-
-        HashSet<String> relatedIfaces = new HashSet<>(Arrays.asList(requiredIfaces));
-        // ConcurrentHashMap's EntrySet iterators are "guaranteed to traverse
-        // elements as they existed upon construction exactly once, and may
-        // (but are not guaranteed to) reflect any modifications subsequent to construction".
-        // This is enough here.
-        for (Map.Entry<String, String> entry : mStackedIfaces.entrySet()) {
-            if (relatedIfaces.contains(entry.getKey())) {
-                relatedIfaces.add(entry.getValue());
-            } else if (relatedIfaces.contains(entry.getValue())) {
-                relatedIfaces.add(entry.getKey());
-            }
-        }
-
-        String[] outArray = new String[relatedIfaces.size()];
-        return relatedIfaces.toArray(outArray);
-    }
-
-    /**
      * Applies 464xlat adjustments with ifaces noted with {@link #noteStackedIface(String, String)}.
      * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map)
      */
@@ -200,16 +165,6 @@
     }
 
     /**
-     * Parse and return interface-level summary {@link NetworkStats} measured
-     * using {@code /proc/net/dev} style hooks, which may include non IP layer
-     * traffic. Values monotonically increase since device boot, and may include
-     * details about inactive interfaces.
-     */
-    public NetworkStats readNetworkStatsSummaryDev() throws IOException {
-        return mDeps.getNetworkStatsDev();
-    }
-
-    /**
      * Parse and return interface-level summary {@link NetworkStats}. Designed
      * to return only IP layer traffic. Values monotonically increase since
      * device boot, and may include details about inactive interfaces.
@@ -255,8 +210,7 @@
             requestSwapActiveStatsMapLocked();
             // Stats are always read from the inactive map, so they must be read after the
             // swap
-            final NetworkStats stats = mDeps.getNetworkStatsDetail(
-                    UID_ALL, INTERFACES_ALL, TAG_ALL);
+            final NetworkStats stats = mDeps.getNetworkStatsDetail();
             // BPF stats are incremental; fold into mPersistSnapshot.
             mPersistSnapshot.setElapsedRealtime(stats.getElapsedRealtime());
             mPersistSnapshot.combineAllValues(stats);
@@ -343,8 +297,7 @@
      * are expected to monotonically increase since device boot.
      */
     @VisibleForTesting
-    public static native int nativeReadNetworkStatsDetail(NetworkStats stats, int limitUid,
-            String[] limitIfaces, int limitTag);
+    public static native int nativeReadNetworkStatsDetail(NetworkStats stats);
 
     @VisibleForTesting
     public static native int nativeReadNetworkStatsDev(NetworkStats stats);
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index cf53002..e7ef510 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -46,6 +46,7 @@
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.NetworkStatsHistory.FIELD_ALL;
 import static android.net.NetworkTemplate.MATCH_MOBILE;
+import static android.net.NetworkTemplate.MATCH_TEST;
 import static android.net.NetworkTemplate.MATCH_WIFI;
 import static android.net.TrafficStats.KB_IN_BYTES;
 import static android.net.TrafficStats.MB_IN_BYTES;
@@ -69,6 +70,7 @@
 import android.annotation.Nullable;
 import android.annotation.TargetApi;
 import android.app.AlarmManager;
+import android.app.BroadcastOptions;
 import android.app.PendingIntent;
 import android.app.usage.NetworkStatsManager;
 import android.content.ApexEnvironment;
@@ -82,7 +84,6 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.net.ConnectivityManager;
-import android.net.ConnectivityResources;
 import android.net.DataUsageRequest;
 import android.net.INetd;
 import android.net.INetworkStatsService;
@@ -105,6 +106,7 @@
 import android.net.TetherStatsParcel;
 import android.net.TetheringManager;
 import android.net.TrafficStats;
+import android.net.TransportInfo;
 import android.net.UnderlyingNetworkInfo;
 import android.net.Uri;
 import android.net.netstats.IUsageCallback;
@@ -112,8 +114,10 @@
 import android.net.netstats.provider.INetworkStatsProvider;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.net.netstats.provider.NetworkStatsProvider;
+import android.net.wifi.WifiInfo;
 import android.os.Binder;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.DropBoxManager;
 import android.os.Environment;
 import android.os.Handler;
@@ -149,6 +153,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FileRotator;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
 import com.android.net.module.util.BestClock;
 import com.android.net.module.util.BinderUtils;
@@ -166,7 +171,11 @@
 import com.android.net.module.util.Struct.U8;
 import com.android.net.module.util.bpf.CookieTagMapKey;
 import com.android.net.module.util.bpf.CookieTagMapValue;
+import com.android.networkstack.apishim.BroadcastOptionsShimImpl;
+import com.android.networkstack.apishim.ConstantsShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
 import com.android.server.BpfNetMaps;
+import com.android.server.connectivity.ConnectivityResources;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -333,13 +342,11 @@
             }
         }
 
-        Config getDevConfig();
         Config getXtConfig();
         Config getUidConfig();
         Config getUidTagConfig();
 
         long getGlobalAlertBytes(long def);
-        long getDevPersistBytes(long def);
         long getXtPersistBytes(long def);
         long getUidPersistBytes(long def);
         long getUidTagPersistBytes(long def);
@@ -389,8 +396,6 @@
     private final Semaphore mStatsProviderSem = new Semaphore(0, true);
 
     @GuardedBy("mStatsLock")
-    private NetworkStatsRecorder mDevRecorder;
-    @GuardedBy("mStatsLock")
     private NetworkStatsRecorder mXtRecorder;
     @GuardedBy("mStatsLock")
     private NetworkStatsRecorder mUidRecorder;
@@ -526,8 +531,24 @@
                 case MSG_BROADCAST_NETWORK_STATS_UPDATED: {
                     final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED);
                     updatedIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                    Bundle opts = null;
+                    if (SdkLevel.isAtLeastU()) {
+                        try {
+                            // This allows us to discard older broadcasts still waiting to
+                            // be delivered.
+                            opts = BroadcastOptionsShimImpl.newInstance(
+                                    BroadcastOptions.makeBasic())
+                                    .setDeliveryGroupPolicy(
+                                            ConstantsShim.DELIVERY_GROUP_POLICY_MOST_RECENT)
+                                    .setDeferralPolicy(
+                                            ConstantsShim.DEFERRAL_POLICY_UNTIL_ACTIVE)
+                                    .toBundle();
+                        } catch (UnsupportedApiLevelException e) {
+                            Log.wtf(TAG, "Using unsupported API" + e);
+                        }
+                    }
                     mContext.sendBroadcastAsUser(updatedIntent, UserHandle.ALL,
-                            READ_NETWORK_USAGE_HISTORY);
+                            READ_NETWORK_USAGE_HISTORY, opts);
                     break;
                 }
             }
@@ -844,8 +865,6 @@
             mSystemReady = true;
 
             // create data recorders along with historical rotators
-            mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false, mStatsDir,
-                    true /* wipeOnError */);
             mXtRecorder = buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false, mStatsDir,
                     true /* wipeOnError */);
             mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false, mStatsDir,
@@ -928,7 +947,11 @@
     @GuardedBy("mStatsLock")
     private void shutdownLocked() {
         final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class);
-        tetheringManager.unregisterTetheringEventCallback(mTetherListener);
+        try {
+            tetheringManager.unregisterTetheringEventCallback(mTetherListener);
+        } catch (IllegalStateException e) {
+            Log.i(TAG, "shutdownLocked: error when unregister tethering, ignored. e=" + e);
+        }
         mContext.unregisterReceiver(mPollReceiver);
         mContext.unregisterReceiver(mRemovedReceiver);
         mContext.unregisterReceiver(mUserReceiver);
@@ -943,7 +966,6 @@
         final long currentTime = mClock.millis();
 
         // persist any pending stats
-        mDevRecorder.forcePersistLocked(currentTime);
         mXtRecorder.forcePersistLocked(currentTime);
         mUidRecorder.forcePersistLocked(currentTime);
         mUidTagRecorder.forcePersistLocked(currentTime);
@@ -1010,8 +1032,17 @@
             Log.i(TAG, "Starting import : attempts " + attempts + "/" + targetAttempts);
         }
 
+        // Still create a legacy dev recorder locally but the service doesn't really use it.
+        // This is for backward compatibility where the OEMs might call readPlatformCollection to
+        // perform proprietary operations and relying on the side-effects to complete the follow-up
+        // import process.
+        final NetworkStatsSettings.Config devConfig =
+                new NetworkStatsSettings.Config(HOUR_IN_MILLIS,
+                15 * DAY_IN_MILLIS, 90 * DAY_IN_MILLIS);
+        final NetworkStatsRecorder devRecorder = buildRecorder(PREFIX_DEV, devConfig,
+                false, mStatsDir, true /* wipeOnError */);
         final MigrationInfo[] migrations = new MigrationInfo[]{
-                new MigrationInfo(mDevRecorder), new MigrationInfo(mXtRecorder),
+                new MigrationInfo(devRecorder), new MigrationInfo(mXtRecorder),
                 new MigrationInfo(mUidRecorder), new MigrationInfo(mUidTagRecorder)
         };
 
@@ -1021,9 +1052,10 @@
             final File legacyBaseDir = mDeps.getLegacyStatsDir();
             // Set wipeOnError flag false so the recorder won't damage persistent data if reads
             // failed and calling deleteAll.
+            // Set DEV legacy recorder as null since the DEV recorder has been removed.
+            // Thus it doesn't need to build DEV legacy recorder for comparing with imported data.
             legacyRecorders = new NetworkStatsRecorder[]{
-                buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false, legacyBaseDir,
-                        false /* wipeOnError */),
+                null /* dev Recorder */,
                 buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false, legacyBaseDir,
                         false /* wipeOnError */),
                 buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false, legacyBaseDir,
@@ -1040,7 +1072,6 @@
             // commit any data to disk until all are read.
             for (int i = 0; i < migrations.length; i++) {
                 final MigrationInfo migration = migrations[i];
-
                 // Read the collection from platform code, and set fallbacks counter if throws
                 // for better debugging.
                 try {
@@ -1074,6 +1105,7 @@
 
             // Find the latest end time.
             for (final MigrationInfo migration : migrations) {
+                if (PREFIX_DEV.equals(migration.recorder.getCookie())) continue;
                 final long migrationEnd = migration.collection.getEndMillis();
                 if (migrationEnd > migrationEndTime) migrationEndTime = migrationEnd;
             }
@@ -1090,7 +1122,8 @@
             for (final MigrationInfo migration : migrations) {
                 migration.imported = true;
                 migration.recorder.removeDataBefore(migrationEndTime);
-                if (migration.collection.isEmpty()) continue;
+                if (migration.collection.isEmpty()
+                        || PREFIX_DEV.equals(migration.recorder.getCookie())) continue;
                 migration.recorder.importCollectionLocked(migration.collection);
             }
 
@@ -1113,6 +1146,7 @@
             if (migrationEndTime > Long.MIN_VALUE) {
                 try {
                     for (final MigrationInfo migration : migrations) {
+                        if (PREFIX_DEV.equals(migration.recorder.getCookie())) continue;
                         if (migration.imported) {
                             migration.recorder.removeDataBefore(migrationEndTime);
                         }
@@ -1123,6 +1157,7 @@
                     // framework will reboot, and if there are remaining tries, the migration
                     // process will retry, which is fine because it's idempotent.
                     for (final MigrationInfo migration : migrations) {
+                        if (PREFIX_DEV.equals(migration.recorder.getCookie())) continue;
                         migration.recorder.recoverAndDeleteData();
                     }
                 }
@@ -1176,11 +1211,14 @@
     /**
      * Compare imported data with the data returned by legacy recorders.
      *
-     * @return true if the data matches, false if the data does not match or throw with exceptions.
+     * @return true if the data matches or if {@code legacyRecorder} is null, false if the data
+     * does not match or throw with exceptions.
      */
     private boolean compareImportedToLegacyStats(@NonNull MigrationInfo migration,
-            @NonNull NetworkStatsRecorder legacyRecorder) {
+            @Nullable NetworkStatsRecorder legacyRecorder) {
         final NetworkStatsCollection legacyStats;
+        // Skip the recorder that doesn't need to be compared.
+        if (legacyRecorder == null) return true;
         try {
             legacyStats = legacyRecorder.getOrLoadCompleteLocked();
         } catch (Throwable e) {
@@ -1545,7 +1583,9 @@
         // For a template with wifi network keys, it is possible for a malicious
         // client to track the user locations via querying data usage. Thus, enforce
         // fine location permission check.
-        if (!template.getWifiNetworkKeys().isEmpty()) {
+        // For a template with MATCH_TEST, since the wifi network key is just a placeholder
+        // to identify a specific test network, it is not related to track user location.
+        if (!template.getWifiNetworkKeys().isEmpty() && template.getMatchRule() != MATCH_TEST) {
             final boolean canAccessFineLocation = mLocationPermissionChecker
                     .checkCallersLocationPermission(callingPackage,
                     null /* featureId */,
@@ -1699,11 +1739,7 @@
         PermissionUtils.enforceNetworkStackPermission(mContext);
         try {
             final String[] ifaceArray = getAllIfacesSinceBoot(transport);
-            // TODO(b/215633405) : mMobileIfaces and mWifiIfaces already contain the stacked
-            // interfaces, so this is not useful, remove it.
-            final String[] ifacesToQuery =
-                    mStatsFactory.augmentWithStackedInterfaces(ifaceArray);
-            final NetworkStats stats = getNetworkStatsUidDetail(ifacesToQuery);
+            final NetworkStats stats = getNetworkStatsUidDetail(ifaceArray);
             // Clear the interfaces of the stats before returning, so callers won't get this
             // information. This is because no caller needs this information for now, and it
             // makes it easier to change the implementation later by using the histories in the
@@ -1834,7 +1870,6 @@
 
             updatePersistThresholdsLocked();
 
-            mDevRecorder.maybePersistLocked(currentTime);
             mXtRecorder.maybePersistLocked(currentTime);
             mUidRecorder.maybePersistLocked(currentTime);
             mUidTagRecorder.maybePersistLocked(currentTime);
@@ -1950,7 +1985,6 @@
      */
     @GuardedBy("mStatsLock")
     private void updatePersistThresholdsLocked() {
-        mDevRecorder.setPersistThreshold(mSettings.getDevPersistBytes(mPersistThreshold));
         mXtRecorder.setPersistThreshold(mSettings.getXtPersistBytes(mPersistThreshold));
         mUidRecorder.setPersistThreshold(mSettings.getUidPersistBytes(mPersistThreshold));
         mUidTagRecorder.setPersistThreshold(mSettings.getUidTagPersistBytes(mPersistThreshold));
@@ -2097,6 +2131,14 @@
             final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, snapshot,
                     isDefault, ratType);
 
+            // If WifiInfo contains a null network key then this identity should not be added into
+            // the network identity set. See b/266598304.
+            final TransportInfo transportInfo = snapshot.getNetworkCapabilities()
+                    .getTransportInfo();
+            if (transportInfo instanceof WifiInfo) {
+                final WifiInfo info = (WifiInfo) transportInfo;
+                if (info.getNetworkKey() == null) continue;
+            }
             // Traffic occurring on the base interface is always counted for
             // both total usage and UID details.
             final String baseIface = snapshot.getLinkProperties().getInterfaceName();
@@ -2230,30 +2272,23 @@
     @GuardedBy("mStatsLock")
     private void recordSnapshotLocked(long currentTime) throws RemoteException {
         // snapshot and record current counters; read UID stats first to
-        // avoid over counting dev stats.
+        // avoid over counting xt stats.
         Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotUid");
         final NetworkStats uidSnapshot = getNetworkStatsUidDetail(INTERFACES_ALL);
         Trace.traceEnd(TRACE_TAG_NETWORK);
         Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotXt");
         final NetworkStats xtSnapshot = readNetworkStatsSummaryXt();
         Trace.traceEnd(TRACE_TAG_NETWORK);
-        Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotDev");
-        final NetworkStats devSnapshot = readNetworkStatsSummaryDev();
-        Trace.traceEnd(TRACE_TAG_NETWORK);
 
-        // Snapshot for dev/xt stats from all custom stats providers. Counts per-interface data
-        // from stats providers that isn't already counted by dev and XT stats.
+        // Snapshot for xt stats from all custom stats providers. Counts per-interface data
+        // from stats providers that isn't already counted by XT stats.
         Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotStatsProvider");
         final NetworkStats providersnapshot = getNetworkStatsFromProviders(STATS_PER_IFACE);
         Trace.traceEnd(TRACE_TAG_NETWORK);
         xtSnapshot.combineAllValues(providersnapshot);
-        devSnapshot.combineAllValues(providersnapshot);
 
-        // For xt/dev, we pass a null VPN array because usage is aggregated by UID, so VPN traffic
+        // For xt, we pass a null VPN array because usage is aggregated by UID, so VPN traffic
         // can't be reattributed to responsible apps.
-        Trace.traceBegin(TRACE_TAG_NETWORK, "recordDev");
-        mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime);
-        Trace.traceEnd(TRACE_TAG_NETWORK);
         Trace.traceBegin(TRACE_TAG_NETWORK, "recordXt");
         mXtRecorder.recordSnapshotLocked(xtSnapshot, mActiveIfaces, currentTime);
         Trace.traceEnd(TRACE_TAG_NETWORK);
@@ -2333,13 +2368,11 @@
         // persist any pending data depending on requested flags
         Trace.traceBegin(TRACE_TAG_NETWORK, "[persisting]");
         if (persistForce) {
-            mDevRecorder.forcePersistLocked(currentTime);
             mXtRecorder.forcePersistLocked(currentTime);
             mUidRecorder.forcePersistLocked(currentTime);
             mUidTagRecorder.forcePersistLocked(currentTime);
         } else {
             if (persistNetwork) {
-                mDevRecorder.maybePersistLocked(currentTime);
                 mXtRecorder.maybePersistLocked(currentTime);
             }
             if (persistUid) {
@@ -2396,33 +2429,49 @@
         final long currentTime = mClock.millis();
 
         NetworkTemplate template;
-        NetworkStats.Entry devTotal;
         NetworkStats.Entry xtTotal;
         NetworkStats.Entry uidTotal;
 
         // collect mobile sample
         template = new NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES).build();
-        devTotal = mDevRecorder.getTotalSinceBootLocked(template);
         xtTotal = mXtRecorder.getTotalSinceBootLocked(template);
         uidTotal = mUidRecorder.getTotalSinceBootLocked(template);
 
-        EventLog.writeEvent(LOG_TAG_NETSTATS_MOBILE_SAMPLE,
-                devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets,
-                xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
-                uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
-                currentTime);
+        if (SdkLevel.isAtLeastU()) {
+            EventLog.writeEvent(LOG_TAG_NETSTATS_MOBILE_SAMPLE,
+                    xtTotal.rxBytes, xtTotal.txBytes, xtTotal.rxPackets, xtTotal.txPackets,
+                    uidTotal.rxBytes, uidTotal.txBytes, uidTotal.rxPackets, uidTotal.txPackets,
+                    currentTime);
+        } else {
+            // To keep the format of event log, here replaces the value of DevRecorder with the
+            // value of XtRecorder because they have the same content in old design.
+            EventLog.writeEvent(LOG_TAG_NETSTATS_MOBILE_SAMPLE,
+                    xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
+                    xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
+                    uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
+                    currentTime);
+        }
 
         // collect wifi sample
         template = new NetworkTemplate.Builder(MATCH_WIFI).build();
-        devTotal = mDevRecorder.getTotalSinceBootLocked(template);
         xtTotal = mXtRecorder.getTotalSinceBootLocked(template);
         uidTotal = mUidRecorder.getTotalSinceBootLocked(template);
 
-        EventLog.writeEvent(LOG_TAG_NETSTATS_WIFI_SAMPLE,
-                devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets,
-                xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
-                uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
-                currentTime);
+        if (SdkLevel.isAtLeastU()) {
+            EventLog.writeEvent(LOG_TAG_NETSTATS_WIFI_SAMPLE,
+                    xtTotal.rxBytes, xtTotal.txBytes, xtTotal.rxPackets, xtTotal.txPackets,
+                    uidTotal.rxBytes, uidTotal.txBytes, uidTotal.rxPackets, uidTotal.txPackets,
+                    currentTime);
+        } else {
+            // To keep the format of event log, here replaces the value of DevRecorder with the
+            // value of XtRecorder because they have the same content in old design.
+            EventLog.writeEvent(LOG_TAG_NETSTATS_WIFI_SAMPLE,
+                    xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
+                    xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
+                    uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
+                    currentTime);
+
+        }
     }
 
     // deleteKernelTagData can ignore ENOENT; otherwise we should log an error
@@ -2721,7 +2770,12 @@
 
             pw.println("Dev stats:");
             pw.increaseIndent();
-            mDevRecorder.dumpLocked(pw, fullHistory);
+            pw.println("Pending bytes: ");
+            if (fullHistory) {
+                pw.println("Complete history:");
+            } else {
+                pw.println("History since boot:");
+            }
             pw.decreaseIndent();
 
             pw.println("Xt stats:");
@@ -2781,7 +2835,6 @@
                 mActiveIfaces);
         dumpInterfaces(proto, NetworkStatsServiceDumpProto.ACTIVE_UID_INTERFACES,
                 mActiveUidIfaces);
-        mDevRecorder.dumpDebugLocked(proto, NetworkStatsServiceDumpProto.DEV_STATS);
         mXtRecorder.dumpDebugLocked(proto, NetworkStatsServiceDumpProto.XT_STATS);
         mUidRecorder.dumpDebugLocked(proto, NetworkStatsServiceDumpProto.UID_STATS);
         mUidTagRecorder.dumpDebugLocked(proto,
@@ -2797,7 +2850,7 @@
             return;
         }
         if (map.isEmpty()) {
-            pw.println("No entries");
+            pw.println("");
             return;
         }
         // If there is a concurrent entry deletion, value could be null. http://b/220084230.
@@ -2916,14 +2969,6 @@
                 });
     }
 
-    private NetworkStats readNetworkStatsSummaryDev() {
-        try {
-            return mStatsFactory.readNetworkStatsSummaryDev();
-        } catch (IOException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
     private NetworkStats readNetworkStatsSummaryXt() {
         try {
             return mStatsFactory.readNetworkStatsSummaryXt();
@@ -3232,12 +3277,8 @@
             return false;
         }
         @Override
-        public Config getDevConfig() {
-            return new Config(HOUR_IN_MILLIS, 15 * DAY_IN_MILLIS, 90 * DAY_IN_MILLIS);
-        }
-        @Override
         public Config getXtConfig() {
-            return getDevConfig();
+            return new Config(HOUR_IN_MILLIS, 15 * DAY_IN_MILLIS, 90 * DAY_IN_MILLIS);
         }
         @Override
         public Config getUidConfig() {
@@ -3248,10 +3289,6 @@
             return new Config(2 * HOUR_IN_MILLIS, 5 * DAY_IN_MILLIS, 15 * DAY_IN_MILLIS);
         }
         @Override
-        public long getDevPersistBytes(long def) {
-            return def;
-        }
-        @Override
         public long getXtPersistBytes(long def) {
             return def;
         }
@@ -3268,4 +3305,7 @@
     private static native long nativeGetTotalStat(int type);
     private static native long nativeGetIfaceStat(String iface, int type);
     private static native long nativeGetUidStat(int uid, int type);
+
+    /** Initializes and registers the Perfetto Network Trace data source */
+    public static native void nativeInitNetworkTracing();
 }
diff --git a/service/Android.bp b/service/Android.bp
index 98bbbac..e1376a1 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -138,6 +138,14 @@
     name: "service-connectivity-pre-jarjar",
     sdk_version: "system_server_current",
     min_sdk_version: "30",
+    // NetworkStackApiShimSettingsForCurrentBranch provides the latest available shims depending on
+    // the branch to "service-connectivity".
+    // There are Tethering.apk and TetheringNext.apk variants for the tethering APEX,
+    // which use NetworkStackApiStableShims and NetworkStackApiCurrentShims respectively.
+    // Note that there can be no service-connectivity-next because it would need to be configured in
+    // default_art_config.mk which doesn't support conditionals, hence this scheme of using a
+    // variable here.
+    defaults: ["NetworkStackApiShimSettingsForCurrentBranch"],
     srcs: [
         "src/**/*.java",
         ":framework-connectivity-shared-srcs",
@@ -157,7 +165,9 @@
         // against the implementation because the two libraries are in the same
         // APEX.
         "framework-connectivity-t.stubs.module_lib",
-        "framework-tethering",
+        // TODO: figure out why just using "framework-tethering" uses the stubs, even though both
+        // service-connectivity and framework-tethering are in the same APEX.
+        "framework-tethering.impl",
         "framework-wifi",
         "unsupportedappusage",
         "ServiceConnectivityResources",
@@ -166,9 +176,10 @@
     static_libs: [
         // Do not add libs here if they are already included
         // in framework-connectivity
+        "androidx.annotation_annotation",
         "connectivity-net-module-utils-bpf",
         "connectivity_native_aidl_interface-lateststable-java",
-        "dnsresolver_aidl_interface-V9-java",
+        "dnsresolver_aidl_interface-V11-java",
         "modules-utils-shell-command-handler",
         "net-utils-device-common",
         "net-utils-device-common-bpf",
@@ -180,7 +191,6 @@
         "PlatformProperties",
         "service-connectivity-protos",
         "service-connectivity-stats-protos",
-        "NetworkStackApiStableShims",
     ],
     apex_available: [
         "com.android.tethering",
@@ -192,26 +202,6 @@
     ],
 }
 
-// TODO: Remove this temporary library and put code into module when test coverage is enough.
-java_library {
-    name: "service-mdns",
-    sdk_version: "system_server_current",
-    min_sdk_version: "30",
-    srcs: [
-        "mdns/**/*.java",
-    ],
-    libs: [
-        "framework-annotations-lib",
-        "framework-connectivity-pre-jarjar",
-        "framework-tethering",
-        "framework-wifi",
-        "service-connectivity-pre-jarjar",
-    ],
-    visibility: [
-        "//packages/modules/Connectivity/tests:__subpackages__",
-    ],
-}
-
 java_library {
     name: "service-connectivity-protos",
     sdk_version: "system_current",
@@ -258,7 +248,7 @@
         "framework-annotations-lib",
         "framework-connectivity.impl",
         "framework-connectivity-t.impl",
-        "framework-tethering",
+        "framework-tethering.impl",
         "framework-wifi",
         "libprotobuf-java-nano",
     ],
diff --git a/service/ServiceConnectivityResources/res/values-eu/strings.xml b/service/ServiceConnectivityResources/res/values-eu/strings.xml
index 9b39fd3..13f9eb4 100644
--- a/service/ServiceConnectivityResources/res/values-eu/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-eu/strings.xml
@@ -36,7 +36,7 @@
     <item msgid="3004933964374161223">"datu-konexioa"</item>
     <item msgid="5624324321165953608">"Wifia"</item>
     <item msgid="5667906231066981731">"Bluetootha"</item>
-    <item msgid="346574747471703768">"Ethernet-a"</item>
+    <item msgid="346574747471703768">"Etherneta"</item>
     <item msgid="5734728378097476003">"VPNa"</item>
   </string-array>
     <string name="network_switch_type_name_unknown" msgid="5116448402191972082">"sare mota ezezaguna"</string>
diff --git a/service/ServiceConnectivityResources/res/values-mcc310-mnc590/config.xml b/service/ServiceConnectivityResources/res/values-mcc310-mnc590/config.xml
new file mode 120000
index 0000000..2b8e406
--- /dev/null
+++ b/service/ServiceConnectivityResources/res/values-mcc310-mnc590/config.xml
@@ -0,0 +1 @@
+../values-mcc310-mnc004/config.xml
\ No newline at end of file
diff --git a/service/ServiceConnectivityResources/res/values-mcc310-mnc599/config.xml b/service/ServiceConnectivityResources/res/values-mcc310-mnc599/config.xml
new file mode 120000
index 0000000..2b8e406
--- /dev/null
+++ b/service/ServiceConnectivityResources/res/values-mcc310-mnc599/config.xml
@@ -0,0 +1 @@
+../values-mcc310-mnc004/config.xml
\ No newline at end of file
diff --git a/service/ServiceConnectivityResources/res/values-mcc311-mnc270/config.xml b/service/ServiceConnectivityResources/res/values-mcc311-mnc270/config.xml
new file mode 120000
index 0000000..2b8e406
--- /dev/null
+++ b/service/ServiceConnectivityResources/res/values-mcc311-mnc270/config.xml
@@ -0,0 +1 @@
+../values-mcc310-mnc004/config.xml
\ No newline at end of file
diff --git a/service/ServiceConnectivityResources/res/values-mcc311-mnc280/config.xml b/service/ServiceConnectivityResources/res/values-mcc311-mnc280/config.xml
new file mode 120000
index 0000000..2b8e406
--- /dev/null
+++ b/service/ServiceConnectivityResources/res/values-mcc311-mnc280/config.xml
@@ -0,0 +1 @@
+../values-mcc310-mnc004/config.xml
\ No newline at end of file
diff --git a/service/ServiceConnectivityResources/res/values-mcc311-mnc480/config.xml b/service/ServiceConnectivityResources/res/values-mcc311-mnc480/config.xml
deleted file mode 100644
index 7e7025f..0000000
--- a/service/ServiceConnectivityResources/res/values-mcc311-mnc480/config.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2021 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 values for ConnectivityService
-     DO NOT EDIT THIS FILE for specific device configuration; instead, use a Runtime Resources
-     Overlay package following the overlayable.xml configuration in the same directory:
-     https://source.android.com/devices/architecture/rros -->
-<resources>
-    <!-- Whether the device should automatically switch away from Wi-Fi networks that lose
-         Internet access. Actual device behaviour is controlled by
-         Settings.Global.NETWORK_AVOID_BAD_WIFI. This is the default value of that setting. -->
-    <integer translatable="false" name="config_networkAvoidBadWifi">0</integer>
-</resources>
\ No newline at end of file
diff --git a/service/ServiceConnectivityResources/res/values-mcc311-mnc480/config.xml b/service/ServiceConnectivityResources/res/values-mcc311-mnc480/config.xml
new file mode 120000
index 0000000..2b8e406
--- /dev/null
+++ b/service/ServiceConnectivityResources/res/values-mcc311-mnc480/config.xml
@@ -0,0 +1 @@
+../values-mcc310-mnc004/config.xml
\ No newline at end of file
diff --git a/service/jarjar-excludes.txt b/service/jarjar-excludes.txt
index b0d6763..7bd3862 100644
--- a/service/jarjar-excludes.txt
+++ b/service/jarjar-excludes.txt
@@ -1,9 +1,3 @@
 # Classes loaded by SystemServer via their hardcoded name, so they can't be jarjared
 com\.android\.server\.ConnectivityServiceInitializer(\$.+)?
 com\.android\.server\.NetworkStatsServiceInitializer(\$.+)?
-
-# Do not jarjar com.android.server, as several unit tests fail because they lose
-# package-private visibility between jarjared and non-jarjared classes.
-# TODO: fix the tests and also jarjar com.android.server, or at least only exclude a package that
-# is specific to the module like com.android.server.connectivity
-com\.android\.server\..+
diff --git a/service/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
index 799ac5c..9ced44e 100644
--- a/service/jni/com_android_server_BpfNetMaps.cpp
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -18,7 +18,8 @@
 
 #include "TrafficController.h"
 
-#include <bpf_shared.h>
+#include "netd.h"
+
 #include <jni.h>
 #include <log/log.h>
 #include <nativehelper/JNIHelp.h>
@@ -53,6 +54,10 @@
   if (!isOk(status)) {
     uid_t uid = getuid();
     ALOGE("BpfNetMaps jni init failure as uid=%d", uid);
+    // We probably only ever get called from system_server (ie. AID_SYSTEM)
+    // or from tests, and never from network_stack (ie. AID_NETWORK_STACK).
+    // However, if we ever do add calls from production network_stack code
+    // we do want to make sure this initializes correctly.
     // TODO: Fix tests to not use this jni lib, so we can unconditionally abort()
     if (uid == AID_SYSTEM || uid == AID_NETWORK_STACK) abort();
   }
@@ -235,7 +240,7 @@
 // clang-format on
 
 int register_com_android_server_BpfNetMaps(JNIEnv* env) {
-    return jniRegisterNativeMethods(env, "com/android/server/BpfNetMaps",
+    return jniRegisterNativeMethods(env, "android/net/connectivity/com/android/server/BpfNetMaps",
                                     gMethods, NELEM(gMethods));
 }
 
diff --git a/service/jni/com_android_server_TestNetworkService.cpp b/service/jni/com_android_server_TestNetworkService.cpp
index bd74d54..08d31a3 100644
--- a/service/jni/com_android_server_TestNetworkService.cpp
+++ b/service/jni/com_android_server_TestNetworkService.cpp
@@ -38,9 +38,14 @@
 #include "jni.h"
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
+#include <bpf/KernelUtils.h>
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedUtfChars.h>
 
+#ifndef IFF_NO_CARRIER
+#define IFF_NO_CARRIER 0x0040
+#endif
+
 namespace android {
 
 //------------------------------------------------------------------------------
@@ -66,17 +71,21 @@
 
     // Allocate interface.
     ifr.ifr_flags = (isTun ? IFF_TUN : IFF_TAP) | IFF_NO_PI;
+    if (!hasCarrier) {
+        // Using IFF_NO_CARRIER is supported starting in kernel version >= 6.0
+        // Up until then, unsupported flags are ignored.
+        if (!bpf::isAtLeastKernelVersion(6, 0, 0)) {
+            throwException(env, EOPNOTSUPP, "IFF_NO_CARRIER not supported", ifr.ifr_name);
+            return -1;
+        }
+        ifr.ifr_flags |= IFF_NO_CARRIER;
+    }
     strlcpy(ifr.ifr_name, iface, IFNAMSIZ);
     if (ioctl(tun.get(), TUNSETIFF, &ifr)) {
         throwException(env, errno, "allocating", ifr.ifr_name);
         return -1;
     }
 
-    if (!hasCarrier) {
-        // disable carrier before setting IFF_UP
-        setTunTapCarrierEnabledImpl(env, iface, tun.get(), hasCarrier);
-    }
-
     // Mark some TAP interfaces as supporting multicast
     if (setIffMulticast && !isTun) {
         base::unique_fd inet6CtrlSock(socket(AF_INET6, SOCK_DGRAM, 0));
@@ -151,8 +160,9 @@
 };
 
 int register_com_android_server_TestNetworkService(JNIEnv* env) {
-    return jniRegisterNativeMethods(env, "com/android/server/TestNetworkService", gMethods,
-                                    NELEM(gMethods));
+    return jniRegisterNativeMethods(env,
+            "android/net/connectivity/com/android/server/TestNetworkService", gMethods,
+            NELEM(gMethods));
 }
 
 }; // namespace android
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index 5cd6e5d..c125bd6 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -19,27 +19,29 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
+#include <linux/if_packet.h>
 #include <linux/if_tun.h>
 #include <linux/ioctl.h>
 #include <log/log.h>
 #include <nativehelper/JNIHelp.h>
 #include <net/if.h>
 #include <spawn.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <sys/wait.h>
+#include <sys/xattr.h>
 #include <string>
+#include <unistd.h>
 
+#include <android-modules-utils/sdk_level.h>
 #include <bpf/BpfMap.h>
 #include <bpf/BpfUtils.h>
-#include <bpf_shared.h>
 #include <netjniutils/netjniutils.h>
 #include <private/android_filesystem_config.h>
 
 #include "libclat/clatutils.h"
 #include "nativehelper/scoped_utf_chars.h"
 
-// Sync from system/netd/include/netid_client.h
-#define MARK_UNSET 0u
-
 // Sync from system/netd/server/NetdConstants.h
 #define __INT_STRLEN(i) sizeof(#i)
 #define _INT_STRLEN(i) __INT_STRLEN(i)
@@ -48,14 +50,97 @@
 #define DEVICEPREFIX "v4-"
 
 namespace android {
-static const char* kClatdPath = "/apex/com.android.tethering/bin/for-system/clatd";
+
+static bool fatal = false;
+
+#define ALOGF(s ...) do { ALOGE(s); fatal = true; } while(0)
+
+enum verify { VERIFY_DIR, VERIFY_BIN, VERIFY_PROG, VERIFY_MAP_RO, VERIFY_MAP_RW };
+
+static void verifyPerms(const char * const path,
+                        const mode_t mode, const uid_t uid, const gid_t gid,
+                        const char * const ctxt,
+                        const verify vtype) {
+    struct stat s = {};
+
+    if (lstat(path, &s)) ALOGF("lstat '%s' errno=%d", path, errno);
+    if (s.st_mode != mode) ALOGF("'%s' mode is 0%o != 0%o", path, s.st_mode, mode);
+    if (s.st_uid != uid) ALOGF("'%s' uid is %d != %d", path, s.st_uid, uid);
+    if (s.st_gid != gid) ALOGF("'%s' gid is %d != %d", path, s.st_gid, gid);
+
+    char b[255] = {};
+    int v = lgetxattr(path, "security.selinux", &b, sizeof(b));
+    if (v < 0) ALOGF("lgetxattr '%s' errno=%d", path, errno);
+    if (strncmp(ctxt, b, sizeof(b))) ALOGF("context of '%s' is '%s' != '%s'", path, b, ctxt);
+
+    int fd = -1;
+
+    switch (vtype) {
+      case VERIFY_DIR: return;
+      case VERIFY_BIN: return;
+      case VERIFY_PROG:   fd = bpf::retrieveProgram(path); break;
+      case VERIFY_MAP_RO: fd = bpf::mapRetrieveRO(path); break;
+      case VERIFY_MAP_RW: fd = bpf::mapRetrieveRW(path); break;
+    }
+
+    if (fd < 0) ALOGF("bpf_obj_get '%s' failed, errno=%d", path, errno);
+
+    if (fd >= 0) close(fd);
+}
+
+#undef ALOGF
+
+static const char* kClatdDir = "/apex/com.android.tethering/bin/for-system";
+static const char* kClatdBin = "/apex/com.android.tethering/bin/for-system/clatd";
+
+#define V(path, md, uid, gid, ctx, vtype) \
+    verifyPerms((path), (md), AID_ ## uid, AID_ ## gid, "u:object_r:" ctx ":s0", VERIFY_ ## vtype)
+
+static void verifyClatPerms() {
+    // We might run as part of tests instead of as part of system server
+    if (getuid() != AID_SYSTEM) return;
+
+    // First verify the clatd directory and binary,
+    // since this is built into the apex file system image,
+    // failures here are 99% likely to be build problems.
+    V(kClatdDir, S_IFDIR|0750, ROOT, SYSTEM, "system_file", DIR);
+    V(kClatdBin, S_IFREG|S_ISUID|S_ISGID|0755, CLAT, CLAT, "clatd_exec", BIN);
+
+    // Move on to verifying that the bpf programs and maps are as expected.
+    // This relies on the kernel and bpfloader.
+
+    // Clat BPF was only mainlined during T.
+    if (!modules::sdklevel::IsAtLeastT()) return;
+
+    V("/sys/fs/bpf", S_IFDIR|S_ISVTX|0777, ROOT, ROOT, "fs_bpf", DIR);
+    V("/sys/fs/bpf/net_shared", S_IFDIR|S_ISVTX|0777, ROOT, ROOT, "fs_bpf_net_shared", DIR);
+
+    // pre-U we do not have selinux privs to getattr on bpf maps/progs
+    // so while the below *should* be as listed, we have no way to actually verify
+    if (!modules::sdklevel::IsAtLeastU()) return;
+
+#define V2(path, md, vtype) \
+    V("/sys/fs/bpf/net_shared/" path, (md), ROOT, SYSTEM, "fs_bpf_net_shared", vtype)
+
+    V2("prog_clatd_schedcls_egress4_clat_rawip",  S_IFREG|0440, PROG);
+    V2("prog_clatd_schedcls_ingress6_clat_rawip", S_IFREG|0440, PROG);
+    V2("prog_clatd_schedcls_ingress6_clat_ether", S_IFREG|0440, PROG);
+    V2("map_clatd_clat_egress4_map",              S_IFREG|0660, MAP_RW);
+    V2("map_clatd_clat_ingress6_map",             S_IFREG|0660, MAP_RW);
+
+#undef V2
+
+    if (fatal) abort();
+}
+
+#undef V
 
 static void throwIOException(JNIEnv* env, const char* msg, int error) {
     jniThrowExceptionFmt(env, "java/io/IOException", "%s: %s", msg, strerror(error));
 }
 
 jstring com_android_server_connectivity_ClatCoordinator_selectIpv4Address(JNIEnv* env,
-                                                                          jobject clazz,
+                                                                          jclass clazz,
                                                                           jstring v4addr,
                                                                           jint prefixlen) {
     ScopedUtfChars address(env, v4addr);
@@ -87,7 +172,7 @@
 
 // Picks a random interface ID that is checksum neutral with the IPv4 address and the NAT64 prefix.
 jstring com_android_server_connectivity_ClatCoordinator_generateIpv6Address(
-        JNIEnv* env, jobject clazz, jstring ifaceStr, jstring v4Str, jstring prefix64Str,
+        JNIEnv* env, jclass clazz, jstring ifaceStr, jstring v4Str, jstring prefix64Str,
         jint mark) {
     ScopedUtfChars iface(env, ifaceStr);
     ScopedUtfChars addr4(env, v4Str);
@@ -128,7 +213,7 @@
 }
 
 static jint com_android_server_connectivity_ClatCoordinator_createTunInterface(JNIEnv* env,
-                                                                               jobject clazz,
+                                                                               jclass clazz,
                                                                                jstring tuniface) {
     ScopedUtfChars v4interface(env, tuniface);
 
@@ -141,7 +226,7 @@
     }
 
     struct ifreq ifr = {
-            .ifr_flags = IFF_TUN,
+            .ifr_flags = static_cast<short>(IFF_TUN | IFF_TUN_EXCL),
     };
     strlcpy(ifr.ifr_name, v4interface.c_str(), sizeof(ifr.ifr_name));
 
@@ -155,7 +240,7 @@
     return fd;
 }
 
-static jint com_android_server_connectivity_ClatCoordinator_detectMtu(JNIEnv* env, jobject clazz,
+static jint com_android_server_connectivity_ClatCoordinator_detectMtu(JNIEnv* env, jclass clazz,
                                                                       jstring platSubnet,
                                                                       jint plat_suffix, jint mark) {
     ScopedUtfChars platSubnetStr(env, platSubnet);
@@ -177,19 +262,32 @@
 }
 
 static jint com_android_server_connectivity_ClatCoordinator_openPacketSocket(JNIEnv* env,
-                                                                              jobject clazz) {
+                                                                              jclass clazz) {
     // Will eventually be bound to htons(ETH_P_IPV6) protocol,
     // but only after appropriate bpf filter is attached.
-    int sock = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+    const int sock = socket(AF_PACKET, SOCK_RAW | SOCK_CLOEXEC, 0);
     if (sock < 0) {
         throwIOException(env, "packet socket failed", errno);
         return -1;
     }
+    const int on = 1;
+    // enable tpacket_auxdata cmsg delivery, which includes L2 header length
+    if (setsockopt(sock, SOL_PACKET, PACKET_AUXDATA, &on, sizeof(on))) {
+        throwIOException(env, "packet socket auxdata enablement failed", errno);
+        close(sock);
+        return -1;
+    }
+    // needed for virtio_net_hdr prepending, which includes checksum metadata
+    if (setsockopt(sock, SOL_PACKET, PACKET_VNET_HDR, &on, sizeof(on))) {
+        throwIOException(env, "packet socket vnet_hdr enablement failed", errno);
+        close(sock);
+        return -1;
+    }
     return sock;
 }
 
 static jint com_android_server_connectivity_ClatCoordinator_openRawSocket6(JNIEnv* env,
-                                                                           jobject clazz,
+                                                                           jclass clazz,
                                                                            jint mark) {
     int sock = socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_RAW);
     if (sock < 0) {
@@ -198,7 +296,7 @@
     }
 
     // TODO: check the mark validation
-    if (mark != MARK_UNSET && setsockopt(sock, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) < 0) {
+    if (setsockopt(sock, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) < 0) {
         throwIOException(env, "could not set mark on raw socket", errno);
         close(sock);
         return -1;
@@ -208,7 +306,7 @@
 }
 
 static void com_android_server_connectivity_ClatCoordinator_addAnycastSetsockopt(
-        JNIEnv* env, jobject clazz, jobject javaFd, jstring addr6, jint ifindex) {
+        JNIEnv* env, jclass clazz, jobject javaFd, jstring addr6, jint ifindex) {
     int sock = netjniutils::GetNativeFileDescriptor(env, javaFd);
     if (sock < 0) {
         jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
@@ -234,7 +332,7 @@
 }
 
 static void com_android_server_connectivity_ClatCoordinator_configurePacketSocket(
-        JNIEnv* env, jobject clazz, jobject javaFd, jstring addr6, jint ifindex) {
+        JNIEnv* env, jclass clazz, jobject javaFd, jstring addr6, jint ifindex) {
     ScopedUtfChars addrStr(env, addr6);
 
     int sock = netjniutils::GetNativeFileDescriptor(env, javaFd);
@@ -258,7 +356,7 @@
 }
 
 static jint com_android_server_connectivity_ClatCoordinator_startClatd(
-        JNIEnv* env, jobject clazz, jobject tunJavaFd, jobject readSockJavaFd,
+        JNIEnv* env, jclass clazz, jobject tunJavaFd, jobject readSockJavaFd,
         jobject writeSockJavaFd, jstring iface, jstring pfx96, jstring v4, jstring v6) {
     ScopedUtfChars ifaceStr(env, iface);
     ScopedUtfChars pfx96Str(env, pfx96);
@@ -355,7 +453,7 @@
 
     // 5. actually perform vfork/dup2/execve
     pid_t pid;
-    if (int ret = posix_spawn(&pid, kClatdPath, &fa, &attr, (char* const*)args, nullptr)) {
+    if (int ret = posix_spawn(&pid, kClatdBin, &fa, &attr, (char* const*)args, nullptr)) {
         posix_spawnattr_destroy(&attr);
         posix_spawn_file_actions_destroy(&fa);
         throwIOException(env, "posix_spawn failed", ret);
@@ -374,11 +472,15 @@
 static constexpr int WAITPID_ATTEMPTS = 50;
 static constexpr int WAITPID_RETRY_INTERVAL_US = 100000;
 
-static void stopClatdProcess(int pid) {
-    int err = kill(pid, SIGTERM);
-    if (err) {
-        err = errno;
+static void com_android_server_connectivity_ClatCoordinator_stopClatd(JNIEnv* env, jclass clazz,
+                                                                      jint pid) {
+    if (pid <= 0) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "Invalid pid");
+        return;
     }
+
+    int err = kill(pid, SIGTERM);
+    if (err) err = errno;
     if (err == ESRCH) {
         ALOGE("clatd child process %d unexpectedly disappeared", pid);
         return;
@@ -395,7 +497,9 @@
     if (ret == 0) {
         ALOGE("Failed to SIGTERM clatd pid=%d, try SIGKILL", pid);
         // TODO: fix that kill failed or waitpid doesn't return.
-        kill(pid, SIGKILL);
+        if (kill(pid, SIGKILL)) {
+            ALOGE("Failed to SIGKILL clatd pid=%d: %s", pid, strerror(errno));
+        }
         ret = waitpid(pid, &status, 0);
     }
     if (ret == -1) {
@@ -405,25 +509,8 @@
     }
 }
 
-static void com_android_server_connectivity_ClatCoordinator_stopClatd(JNIEnv* env, jobject clazz,
-                                                                      jstring iface, jstring pfx96,
-                                                                      jstring v4, jstring v6,
-                                                                      jint pid) {
-    ScopedUtfChars ifaceStr(env, iface);
-    ScopedUtfChars pfx96Str(env, pfx96);
-    ScopedUtfChars v4Str(env, v4);
-    ScopedUtfChars v6Str(env, v6);
-
-    if (pid <= 0) {
-        jniThrowExceptionFmt(env, "java/io/IOException", "Invalid pid");
-        return;
-    }
-
-    stopClatdProcess(pid);
-}
-
 static jlong com_android_server_connectivity_ClatCoordinator_getSocketCookie(
-        JNIEnv* env, jobject clazz, jobject sockJavaFd) {
+        JNIEnv* env, jclass clazz, jobject sockJavaFd) {
     int sockFd = netjniutils::GetNativeFileDescriptor(env, sockJavaFd);
     if (sockFd < 0) {
         jniThrowExceptionFmt(env, "java/io/IOException", "Invalid socket file descriptor");
@@ -431,7 +518,7 @@
     }
 
     uint64_t sock_cookie = bpf::getSocketCookie(sockFd);
-    if (sock_cookie == bpf::NONEXISTENT_COOKIE) {
+    if (!sock_cookie) {
         throwIOException(env, "get socket cookie failed", errno);
         return -1;
     }
@@ -466,16 +553,17 @@
          "(Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/lang/"
          "String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
          (void*)com_android_server_connectivity_ClatCoordinator_startClatd},
-        {"native_stopClatd",
-         "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V",
+        {"native_stopClatd", "(I)V",
          (void*)com_android_server_connectivity_ClatCoordinator_stopClatd},
         {"native_getSocketCookie", "(Ljava/io/FileDescriptor;)J",
          (void*)com_android_server_connectivity_ClatCoordinator_getSocketCookie},
 };
 
 int register_com_android_server_connectivity_ClatCoordinator(JNIEnv* env) {
-    return jniRegisterNativeMethods(env, "com/android/server/connectivity/ClatCoordinator",
-                                    gMethods, NELEM(gMethods));
+    verifyClatPerms();
+    return jniRegisterNativeMethods(env,
+            "android/net/connectivity/com/android/server/connectivity/ClatCoordinator",
+            gMethods, NELEM(gMethods));
 }
 
 };  // namespace android
diff --git a/service/jni/onload.cpp b/service/jni/onload.cpp
index 3d15d43..ed74430 100644
--- a/service/jni/onload.cpp
+++ b/service/jni/onload.cpp
@@ -22,8 +22,8 @@
 namespace android {
 
 int register_com_android_server_TestNetworkService(JNIEnv* env);
-int register_com_android_server_connectivity_ClatCoordinator(JNIEnv* env);
 int register_com_android_server_BpfNetMaps(JNIEnv* env);
+int register_com_android_server_connectivity_ClatCoordinator(JNIEnv* env);
 int register_android_server_net_NetworkStatsFactory(JNIEnv* env);
 int register_android_server_net_NetworkStatsService(JNIEnv* env);
 
@@ -38,15 +38,15 @@
         return JNI_ERR;
     }
 
-    if (register_com_android_server_connectivity_ClatCoordinator(env) < 0) {
-        return JNI_ERR;
-    }
-
-    if (register_com_android_server_BpfNetMaps(env) < 0) {
-        return JNI_ERR;
-    }
-
     if (android::modules::sdklevel::IsAtLeastT()) {
+        if (register_com_android_server_BpfNetMaps(env) < 0) {
+            return JNI_ERR;
+        }
+
+        if (register_com_android_server_connectivity_ClatCoordinator(env) < 0) {
+            return JNI_ERR;
+        }
+
         if (register_android_server_net_NetworkStatsFactory(env) < 0) {
             return JNI_ERR;
         }
diff --git a/service/libconnectivity/src/connectivity_native.cpp b/service/libconnectivity/src/connectivity_native.cpp
index 49622ae..f39bb0a 100644
--- a/service/libconnectivity/src/connectivity_native.cpp
+++ b/service/libconnectivity/src/connectivity_native.cpp
@@ -24,8 +24,8 @@
 
 
 static std::shared_ptr<IConnectivityNative> getBinder() {
-    static ndk::SpAIBinder sBinder = ndk::SpAIBinder(reinterpret_cast<AIBinder*>(
-        AServiceManager_getService("connectivity_native")));
+    ndk::SpAIBinder sBinder = ndk::SpAIBinder(reinterpret_cast<AIBinder*>(
+        AServiceManager_checkService("connectivity_native")));
     return aidl::android::net::connectivity::aidl::IConnectivityNative::fromBinder(sBinder);
 }
 
@@ -47,24 +47,36 @@
 int AConnectivityNative_blockPortForBind(in_port_t port) {
     if (!android::modules::sdklevel::IsAtLeastU()) return ENOSYS;
     std::shared_ptr<IConnectivityNative> c = getBinder();
+    if (!c) {
+        return EAGAIN;
+    }
     return getErrno(c->blockPortForBind(port));
 }
 
 int AConnectivityNative_unblockPortForBind(in_port_t port) {
     if (!android::modules::sdklevel::IsAtLeastU()) return ENOSYS;
     std::shared_ptr<IConnectivityNative> c = getBinder();
+    if (!c) {
+        return EAGAIN;
+    }
     return getErrno(c->unblockPortForBind(port));
 }
 
 int AConnectivityNative_unblockAllPortsForBind() {
     if (!android::modules::sdklevel::IsAtLeastU()) return ENOSYS;
     std::shared_ptr<IConnectivityNative> c = getBinder();
+    if (!c) {
+        return EAGAIN;
+    }
     return getErrno(c->unblockAllPortsForBind());
 }
 
 int AConnectivityNative_getPortsBlockedForBind(in_port_t *ports, size_t *count) {
     if (!android::modules::sdklevel::IsAtLeastU()) return ENOSYS;
     std::shared_ptr<IConnectivityNative> c = getBinder();
+    if (!c) {
+        return EAGAIN;
+    }
     std::vector<int32_t> actualBlockedPorts;
     int err = getErrno(c->getPortsBlockedForBind(&actualBlockedPorts));
     if (err) {
diff --git a/service/mdns/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java b/service/mdns/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
deleted file mode 100644
index f7871f3..0000000
--- a/service/mdns/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.connectivity.mdns;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.text.TextUtils;
-import android.util.Pair;
-
-import com.android.server.connectivity.mdns.util.MdnsLogger;
-
-import java.io.IOException;
-import java.lang.ref.WeakReference;
-import java.net.DatagramPacket;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.Callable;
-
-/**
- * A {@link Callable} that builds and enqueues a mDNS query to send over the multicast socket. If a
- * query is built and enqueued successfully, then call to {@link #call()} returns the transaction ID
- * and the list of the subtypes in the query as a {@link Pair}. If a query is failed to build, or if
- * it can not be enqueued, then call to {@link #call()} returns {@code null}.
- */
-public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<String>>> {
-
-    private static final String TAG = "MdnsQueryCallable";
-    private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
-    private static final List<Integer> castShellEmulatorMdnsPorts;
-
-    static {
-        castShellEmulatorMdnsPorts = new ArrayList<>();
-        String[] stringPorts = MdnsConfigs.castShellEmulatorMdnsPorts();
-
-        for (String port : stringPorts) {
-            try {
-                castShellEmulatorMdnsPorts.add(Integer.parseInt(port));
-            } catch (NumberFormatException e) {
-                // Ignore.
-            }
-        }
-    }
-
-    private final WeakReference<MdnsSocketClient> weakRequestSender;
-    private final MdnsPacketWriter packetWriter;
-    private final String[] serviceTypeLabels;
-    private final List<String> subtypes;
-    private final boolean expectUnicastResponse;
-    private final int transactionId;
-
-    EnqueueMdnsQueryCallable(
-            @NonNull MdnsSocketClient requestSender,
-            @NonNull MdnsPacketWriter packetWriter,
-            @NonNull String serviceType,
-            @NonNull Collection<String> subtypes,
-            boolean expectUnicastResponse,
-            int transactionId) {
-        weakRequestSender = new WeakReference<>(requestSender);
-        this.packetWriter = packetWriter;
-        serviceTypeLabels = TextUtils.split(serviceType, "\\.");
-        this.subtypes = new ArrayList<>(subtypes);
-        this.expectUnicastResponse = expectUnicastResponse;
-        this.transactionId = transactionId;
-    }
-
-    // Incompatible return type for override of Callable#call().
-    @SuppressWarnings("nullness:override.return.invalid")
-    @Override
-    @Nullable
-    public Pair<Integer, List<String>> call() {
-        try {
-            MdnsSocketClient requestSender = weakRequestSender.get();
-            if (requestSender == null) {
-                return null;
-            }
-
-            int numQuestions = 1;
-            if (!subtypes.isEmpty()) {
-                numQuestions += subtypes.size();
-            }
-
-            // Header.
-            packetWriter.writeUInt16(transactionId); // transaction ID
-            packetWriter.writeUInt16(MdnsConstants.FLAGS_QUERY); // flags
-            packetWriter.writeUInt16(numQuestions); // number of questions
-            packetWriter.writeUInt16(0); // number of answers (not yet known; will be written later)
-            packetWriter.writeUInt16(0); // number of authority entries
-            packetWriter.writeUInt16(0); // number of additional records
-
-            // Question(s). There will be one question for each (fqdn+subtype, recordType)
-          // combination,
-            // as well as one for each (fqdn, recordType) combination.
-
-            for (String subtype : subtypes) {
-                String[] labels = new String[serviceTypeLabels.length + 2];
-                labels[0] = MdnsConstants.SUBTYPE_PREFIX + subtype;
-                labels[1] = MdnsConstants.SUBTYPE_LABEL;
-                System.arraycopy(serviceTypeLabels, 0, labels, 2, serviceTypeLabels.length);
-
-                packetWriter.writeLabels(labels);
-                packetWriter.writeUInt16(MdnsRecord.TYPE_PTR);
-                packetWriter.writeUInt16(
-                        MdnsConstants.QCLASS_INTERNET
-                                | (expectUnicastResponse ? MdnsConstants.QCLASS_UNICAST : 0));
-            }
-
-            packetWriter.writeLabels(serviceTypeLabels);
-            packetWriter.writeUInt16(MdnsRecord.TYPE_PTR);
-            packetWriter.writeUInt16(
-                    MdnsConstants.QCLASS_INTERNET
-                            | (expectUnicastResponse ? MdnsConstants.QCLASS_UNICAST : 0));
-
-            InetAddress mdnsAddress = MdnsConstants.getMdnsIPv4Address();
-            if (requestSender.isOnIPv6OnlyNetwork()) {
-                mdnsAddress = MdnsConstants.getMdnsIPv6Address();
-            }
-
-            sendPacketTo(requestSender,
-                    new InetSocketAddress(mdnsAddress, MdnsConstants.MDNS_PORT));
-            for (Integer emulatorPort : castShellEmulatorMdnsPorts) {
-                sendPacketTo(requestSender, new InetSocketAddress(mdnsAddress, emulatorPort));
-            }
-            return Pair.create(transactionId, subtypes);
-        } catch (IOException e) {
-            LOGGER.e(String.format("Failed to create mDNS packet for subtype: %s.",
-                    TextUtils.join(",", subtypes)), e);
-            return null;
-        }
-    }
-
-    private void sendPacketTo(MdnsSocketClient requestSender, InetSocketAddress address)
-            throws IOException {
-        DatagramPacket packet = packetWriter.getPacket(address);
-        if (expectUnicastResponse) {
-            requestSender.sendUnicastPacket(packet);
-        } else {
-            requestSender.sendMulticastPacket(packet);
-        }
-    }
-}
\ No newline at end of file
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service/mdns/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
deleted file mode 100644
index 0f3c23a..0000000
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.connectivity.mdns;
-
-import android.Manifest.permission;
-import android.annotation.NonNull;
-import android.annotation.RequiresPermission;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.connectivity.mdns.util.MdnsLogger;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Map;
-
-/**
- * This class keeps tracking the set of registered {@link MdnsServiceBrowserListener} instances, and
- * notify them when a mDNS service instance is found, updated, or removed?
- */
-public class MdnsDiscoveryManager implements MdnsSocketClient.Callback {
-    private static final String TAG = MdnsDiscoveryManager.class.getSimpleName();
-    public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final MdnsLogger LOGGER = new MdnsLogger("MdnsDiscoveryManager");
-
-    private final ExecutorProvider executorProvider;
-    private final MdnsSocketClient socketClient;
-
-    private final Map<String, MdnsServiceTypeClient> serviceTypeClients = new ArrayMap<>();
-
-    public MdnsDiscoveryManager(
-            @NonNull ExecutorProvider executorProvider, @NonNull MdnsSocketClient socketClient) {
-        this.executorProvider = executorProvider;
-        this.socketClient = socketClient;
-    }
-
-    /**
-     * Starts (or continue) to discovery mDNS services with given {@code serviceType}, and registers
-     * {@code listener} for receiving mDNS service discovery responses.
-     *
-     * @param serviceType   The type of the service to discover.
-     * @param listener      The {@link MdnsServiceBrowserListener} listener.
-     * @param searchOptions The {@link MdnsSearchOptions} to be used for discovering {@code
-     *                      serviceType}.
-     */
-    @RequiresPermission(permission.CHANGE_WIFI_MULTICAST_STATE)
-    public synchronized void registerListener(
-            @NonNull String serviceType,
-            @NonNull MdnsServiceBrowserListener listener,
-            @NonNull MdnsSearchOptions searchOptions) {
-        LOGGER.log(
-                "Registering listener for subtypes: %s",
-                TextUtils.join(",", searchOptions.getSubtypes()));
-        if (serviceTypeClients.isEmpty()) {
-            // First listener. Starts the socket client.
-            try {
-                socketClient.startDiscovery();
-            } catch (IOException e) {
-                LOGGER.e("Failed to start discover.", e);
-                return;
-            }
-        }
-        // All listeners of the same service types shares the same MdnsServiceTypeClient.
-        MdnsServiceTypeClient serviceTypeClient = serviceTypeClients.get(serviceType);
-        if (serviceTypeClient == null) {
-            serviceTypeClient = createServiceTypeClient(serviceType);
-            serviceTypeClients.put(serviceType, serviceTypeClient);
-        }
-        serviceTypeClient.startSendAndReceive(listener, searchOptions);
-    }
-
-    /**
-     * Unregister {@code listener} for receiving mDNS service discovery responses. IF no listener is
-     * registered for the given service type, stops discovery for the service type.
-     *
-     * @param serviceType The type of the service to discover.
-     * @param listener    The {@link MdnsServiceBrowserListener} listener.
-     */
-    @RequiresPermission(permission.CHANGE_WIFI_MULTICAST_STATE)
-    public synchronized void unregisterListener(
-            @NonNull String serviceType, @NonNull MdnsServiceBrowserListener listener) {
-        LOGGER.log("Unregistering listener for service type: %s", serviceType);
-        MdnsServiceTypeClient serviceTypeClient = serviceTypeClients.get(serviceType);
-        if (serviceTypeClient == null) {
-            return;
-        }
-        if (serviceTypeClient.stopSendAndReceive(listener)) {
-            // No listener is registered for the service type anymore, remove it from the list of
-          // the
-            // service type clients.
-            serviceTypeClients.remove(serviceType);
-            if (serviceTypeClients.isEmpty()) {
-                // No discovery request. Stops the socket client.
-                socketClient.stopDiscovery();
-            }
-        }
-    }
-
-    @Override
-    public synchronized void onResponseReceived(@NonNull MdnsResponse response) {
-        String[] name =
-                response.getPointerRecords().isEmpty()
-                        ? null
-                        : response.getPointerRecords().get(0).getName();
-        if (name != null) {
-            for (MdnsServiceTypeClient serviceTypeClient : serviceTypeClients.values()) {
-                String[] serviceType = serviceTypeClient.getServiceTypeLabels();
-                if ((Arrays.equals(name, serviceType)
-                        || ((name.length == (serviceType.length + 2))
-                        && name[1].equals(MdnsConstants.SUBTYPE_LABEL)
-                        && MdnsRecord.labelsAreSuffix(serviceType, name)))) {
-                    serviceTypeClient.processResponse(response);
-                    return;
-                }
-            }
-        }
-    }
-
-    @Override
-    public synchronized void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode) {
-        for (MdnsServiceTypeClient serviceTypeClient : serviceTypeClients.values()) {
-            serviceTypeClient.onFailedToParseMdnsResponse(receivedPacketNumber, errorCode);
-        }
-    }
-
-    @VisibleForTesting
-    MdnsServiceTypeClient createServiceTypeClient(@NonNull String serviceType) {
-        return new MdnsServiceTypeClient(
-                serviceType, socketClient,
-                executorProvider.newServiceTypeClientSchedulerExecutor());
-    }
-}
\ No newline at end of file
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsPacket.java b/service/mdns/com/android/server/connectivity/mdns/MdnsPacket.java
deleted file mode 100644
index eae084a..0000000
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsPacket.java
+++ /dev/null
@@ -1,43 +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 com.android.server.connectivity.mdns;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A class holding data that can be included in a mDNS packet.
- */
-public class MdnsPacket {
-    public final int flags;
-    public final List<MdnsRecord> questions;
-    public final List<MdnsRecord> answers;
-    public final List<MdnsRecord> authorityRecords;
-    public final List<MdnsRecord> additionalRecords;
-
-    MdnsPacket(int flags,
-            List<MdnsRecord> questions,
-            List<MdnsRecord> answers,
-            List<MdnsRecord> authorityRecords,
-            List<MdnsRecord> additionalRecords) {
-        this.flags = flags;
-        this.questions = Collections.unmodifiableList(questions);
-        this.answers = Collections.unmodifiableList(answers);
-        this.authorityRecords = Collections.unmodifiableList(authorityRecords);
-        this.additionalRecords = Collections.unmodifiableList(additionalRecords);
-    }
-}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsReplySender.java b/service/mdns/com/android/server/connectivity/mdns/MdnsReplySender.java
deleted file mode 100644
index 2acd789..0000000
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsReplySender.java
+++ /dev/null
@@ -1,88 +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 com.android.server.connectivity.mdns;
-
-import android.annotation.NonNull;
-import android.os.Looper;
-
-import java.io.IOException;
-import java.net.DatagramPacket;
-import java.net.MulticastSocket;
-import java.net.SocketAddress;
-
-/**
- * A class that handles sending mDNS replies to a {@link MulticastSocket}, possibly queueing them
- * to be sent after some delay.
- *
- * TODO: implement sending after a delay, combining queued replies and duplicate answer suppression
- */
-public class MdnsReplySender {
-    @NonNull
-    private final MulticastSocket mSocket;
-    @NonNull
-    private final Looper mLooper;
-    @NonNull
-    private final byte[] mPacketCreationBuffer;
-
-    public MdnsReplySender(@NonNull Looper looper,
-            @NonNull MulticastSocket socket, @NonNull byte[] packetCreationBuffer) {
-        mLooper = looper;
-        mSocket = socket;
-        mPacketCreationBuffer = packetCreationBuffer;
-    }
-
-    /**
-     * Send a packet immediately.
-     *
-     * Must be called on the looper thread used by the {@link MdnsReplySender}.
-     */
-    public void sendNow(@NonNull MdnsPacket packet, @NonNull SocketAddress destination)
-            throws IOException {
-        if (Thread.currentThread() != mLooper.getThread()) {
-            throw new IllegalStateException("sendNow must be called in the handler thread");
-        }
-
-        // TODO: support packets over size (send in multiple packets with TC bit set)
-        final MdnsPacketWriter writer = new MdnsPacketWriter(mPacketCreationBuffer);
-
-        writer.writeUInt16(0); // Transaction ID (advertisement: 0)
-        writer.writeUInt16(packet.flags); // Response, authoritative (rfc6762 18.4)
-        writer.writeUInt16(packet.questions.size()); // questions count
-        writer.writeUInt16(packet.answers.size()); // answers count
-        writer.writeUInt16(packet.authorityRecords.size()); // authority entries count
-        writer.writeUInt16(packet.additionalRecords.size()); // additional records count
-
-        for (MdnsRecord record : packet.questions) {
-            record.write(writer, 0L);
-        }
-        for (MdnsRecord record : packet.answers) {
-            record.write(writer, 0L);
-        }
-        for (MdnsRecord record : packet.authorityRecords) {
-            record.write(writer, 0L);
-        }
-        for (MdnsRecord record : packet.additionalRecords) {
-            record.write(writer, 0L);
-        }
-
-        final int len = writer.getWritePosition();
-        final byte[] outBuffer = new byte[len];
-        System.arraycopy(mPacketCreationBuffer, 0, outBuffer, 0, len);
-
-        mSocket.send(new DatagramPacket(outBuffer, 0, len, destination));
-    }
-}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsResponse.java b/service/mdns/com/android/server/connectivity/mdns/MdnsResponse.java
deleted file mode 100644
index 3a41978..0000000
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsResponse.java
+++ /dev/null
@@ -1,403 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.connectivity.mdns;
-
-import android.annotation.Nullable;
-import android.net.Network;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-
-/** An mDNS response. */
-public class MdnsResponse {
-    private final List<MdnsRecord> records;
-    private final List<MdnsPointerRecord> pointerRecords;
-    private MdnsServiceRecord serviceRecord;
-    private MdnsTextRecord textRecord;
-    private MdnsInetAddressRecord inet4AddressRecord;
-    private MdnsInetAddressRecord inet6AddressRecord;
-    private long lastUpdateTime;
-    private final int interfaceIndex;
-    @Nullable private final Network network;
-
-    /** Constructs a new, empty response. */
-    public MdnsResponse(long now, int interfaceIndex, @Nullable Network network) {
-        lastUpdateTime = now;
-        records = new LinkedList<>();
-        pointerRecords = new LinkedList<>();
-        this.interfaceIndex = interfaceIndex;
-        this.network = network;
-    }
-
-    // This generic typed helper compares records for equality.
-    // Returns True if records are the same.
-    private <T> boolean recordsAreSame(T a, T b) {
-        return ((a == null) && (b == null)) || ((a != null) && (b != null) && a.equals(b));
-    }
-
-    /**
-     * Adds a pointer record.
-     *
-     * @return <code>true</code> if the record was added, or <code>false</code> if a matching
-     * pointer
-     * record is already present in the response.
-     */
-    public synchronized boolean addPointerRecord(MdnsPointerRecord pointerRecord) {
-        if (!pointerRecords.contains(pointerRecord)) {
-            pointerRecords.add(pointerRecord);
-            records.add(pointerRecord);
-            return true;
-        }
-
-        return false;
-    }
-
-    /** Gets the pointer records. */
-    public synchronized List<MdnsPointerRecord> getPointerRecords() {
-        // Returns a shallow copy.
-        return new LinkedList<>(pointerRecords);
-    }
-
-    public synchronized boolean hasPointerRecords() {
-        return !pointerRecords.isEmpty();
-    }
-
-    @VisibleForTesting
-    synchronized void clearPointerRecords() {
-        pointerRecords.clear();
-    }
-
-    public synchronized boolean hasSubtypes() {
-        for (MdnsPointerRecord pointerRecord : pointerRecords) {
-            if (pointerRecord.hasSubtype()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Nullable
-    public synchronized List<String> getSubtypes() {
-        List<String> subtypes = null;
-        for (MdnsPointerRecord pointerRecord : pointerRecords) {
-            String pointerRecordSubtype = pointerRecord.getSubtype();
-            if (pointerRecordSubtype != null) {
-                if (subtypes == null) {
-                    subtypes = new LinkedList<>();
-                }
-                subtypes.add(pointerRecordSubtype);
-            }
-        }
-
-        return subtypes;
-    }
-
-    @VisibleForTesting
-    public synchronized void removeSubtypes() {
-        Iterator<MdnsPointerRecord> iter = pointerRecords.iterator();
-        while (iter.hasNext()) {
-            MdnsPointerRecord pointerRecord = iter.next();
-            if (pointerRecord.hasSubtype()) {
-                iter.remove();
-            }
-        }
-    }
-
-    /** Sets the service record. */
-    public synchronized boolean setServiceRecord(MdnsServiceRecord serviceRecord) {
-        if (recordsAreSame(this.serviceRecord, serviceRecord)) {
-            return false;
-        }
-        if (this.serviceRecord != null) {
-            records.remove(this.serviceRecord);
-        }
-        this.serviceRecord = serviceRecord;
-        if (this.serviceRecord != null) {
-            records.add(this.serviceRecord);
-        }
-        return true;
-    }
-
-    /** Gets the service record. */
-    public synchronized MdnsServiceRecord getServiceRecord() {
-        return serviceRecord;
-    }
-
-    public synchronized boolean hasServiceRecord() {
-        return serviceRecord != null;
-    }
-
-    /** Sets the text record. */
-    public synchronized boolean setTextRecord(MdnsTextRecord textRecord) {
-        if (recordsAreSame(this.textRecord, textRecord)) {
-            return false;
-        }
-        if (this.textRecord != null) {
-            records.remove(this.textRecord);
-        }
-        this.textRecord = textRecord;
-        if (this.textRecord != null) {
-            records.add(this.textRecord);
-        }
-        return true;
-    }
-
-    /** Gets the text record. */
-    public synchronized MdnsTextRecord getTextRecord() {
-        return textRecord;
-    }
-
-    public synchronized boolean hasTextRecord() {
-        return textRecord != null;
-    }
-
-    /** Sets the IPv4 address record. */
-    public synchronized boolean setInet4AddressRecord(
-            @Nullable MdnsInetAddressRecord newInet4AddressRecord) {
-        if (recordsAreSame(this.inet4AddressRecord, newInet4AddressRecord)) {
-            return false;
-        }
-        if (this.inet4AddressRecord != null) {
-            records.remove(this.inet4AddressRecord);
-        }
-        if (newInet4AddressRecord != null && newInet4AddressRecord.getInet4Address() != null) {
-            this.inet4AddressRecord = newInet4AddressRecord;
-            records.add(this.inet4AddressRecord);
-        }
-        return true;
-    }
-
-    /** Gets the IPv4 address record. */
-    public synchronized MdnsInetAddressRecord getInet4AddressRecord() {
-        return inet4AddressRecord;
-    }
-
-    public synchronized boolean hasInet4AddressRecord() {
-        return inet4AddressRecord != null;
-    }
-
-    /** Sets the IPv6 address record. */
-    public synchronized boolean setInet6AddressRecord(
-            @Nullable MdnsInetAddressRecord newInet6AddressRecord) {
-        if (recordsAreSame(this.inet6AddressRecord, newInet6AddressRecord)) {
-            return false;
-        }
-        if (this.inet6AddressRecord != null) {
-            records.remove(this.inet6AddressRecord);
-        }
-        if (newInet6AddressRecord != null && newInet6AddressRecord.getInet6Address() != null) {
-            this.inet6AddressRecord = newInet6AddressRecord;
-            records.add(this.inet6AddressRecord);
-        }
-        return true;
-    }
-
-    /**
-     * Returns the index of the network interface at which this response was received. Can be set to
-     * {@link MdnsSocket#INTERFACE_INDEX_UNSPECIFIED} if unset.
-     */
-    public int getInterfaceIndex() {
-        return interfaceIndex;
-    }
-
-    /**
-     * Returns the network at which this response was received, or null if the network is unknown.
-     */
-    @Nullable
-    public Network getNetwork() {
-        return network;
-    }
-
-    /** Gets the IPv6 address record. */
-    public synchronized MdnsInetAddressRecord getInet6AddressRecord() {
-        return inet6AddressRecord;
-    }
-
-    public synchronized boolean hasInet6AddressRecord() {
-        return inet6AddressRecord != null;
-    }
-
-    /** Gets all of the records. */
-    public synchronized List<MdnsRecord> getRecords() {
-        return new LinkedList<>(records);
-    }
-
-    /**
-     * Merges any records that are present in another response into this one.
-     *
-     * @return <code>true</code> if any records were added or updated.
-     */
-    public synchronized boolean mergeRecordsFrom(MdnsResponse other) {
-        lastUpdateTime = other.lastUpdateTime;
-
-        boolean updated = false;
-
-        List<MdnsPointerRecord> pointerRecords = other.getPointerRecords();
-        if (pointerRecords != null) {
-            for (MdnsPointerRecord pointerRecord : pointerRecords) {
-                if (addPointerRecord(pointerRecord)) {
-                    updated = true;
-                }
-            }
-        }
-
-        MdnsServiceRecord serviceRecord = other.getServiceRecord();
-        if (serviceRecord != null) {
-            if (setServiceRecord(serviceRecord)) {
-                updated = true;
-            }
-        }
-
-        MdnsTextRecord textRecord = other.getTextRecord();
-        if (textRecord != null) {
-            if (setTextRecord(textRecord)) {
-                updated = true;
-            }
-        }
-
-        MdnsInetAddressRecord otherInet4AddressRecord = other.getInet4AddressRecord();
-        if (otherInet4AddressRecord != null && otherInet4AddressRecord.getInet4Address() != null) {
-            if (setInet4AddressRecord(otherInet4AddressRecord)) {
-                updated = true;
-            }
-        }
-
-        MdnsInetAddressRecord otherInet6AddressRecord = other.getInet6AddressRecord();
-        if (otherInet6AddressRecord != null && otherInet6AddressRecord.getInet6Address() != null) {
-            if (setInet6AddressRecord(otherInet6AddressRecord)) {
-                updated = true;
-            }
-        }
-
-        // If the hostname in the service record no longer matches the hostname in either of the
-        // address records, then drop the address records.
-        if (this.serviceRecord != null) {
-            boolean dropAddressRecords = false;
-
-            if (this.inet4AddressRecord != null) {
-                if (!Arrays.equals(
-                        this.serviceRecord.getServiceHost(), this.inet4AddressRecord.getName())) {
-                    dropAddressRecords = true;
-                }
-            }
-            if (this.inet6AddressRecord != null) {
-                if (!Arrays.equals(
-                        this.serviceRecord.getServiceHost(), this.inet6AddressRecord.getName())) {
-                    dropAddressRecords = true;
-                }
-            }
-
-            if (dropAddressRecords) {
-                setInet4AddressRecord(null);
-                setInet6AddressRecord(null);
-                updated = true;
-            }
-        }
-
-        return updated;
-    }
-
-    /**
-     * Tests if the response is complete. A response is considered complete if it contains PTR, SRV,
-     * TXT, and A (for IPv4) or AAAA (for IPv6) records.
-     */
-    public synchronized boolean isComplete() {
-        return !pointerRecords.isEmpty()
-                && (serviceRecord != null)
-                && (textRecord != null)
-                && (inet4AddressRecord != null || inet6AddressRecord != null);
-    }
-
-    /**
-     * Returns the key for this response. The key uniquely identifies the response by its service
-     * name.
-     */
-    public synchronized String getServiceInstanceName() {
-        if (pointerRecords.isEmpty()) {
-            return null;
-        }
-        String[] pointers = pointerRecords.get(0).getPointer();
-        return ((pointers != null) && (pointers.length > 0)) ? pointers[0] : null;
-    }
-
-    /**
-     * Tests if this response is a goodbye message. This will be true if a service record is present
-     * and any of the records have a TTL of 0.
-     */
-    public synchronized boolean isGoodbye() {
-        if (getServiceInstanceName() != null) {
-            for (MdnsRecord record : records) {
-                // Expiring PTR records with subtypes just signal a change in known supported
-                // criteria, not the device itself going offline, so ignore those.
-                if ((record instanceof MdnsPointerRecord)
-                        && ((MdnsPointerRecord) record).hasSubtype()) {
-                    continue;
-                }
-
-                if (record.getTtl() == 0) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Writes the response to a packet.
-     *
-     * @param writer The writer to use.
-     * @param now    The current time. This is used to write updated TTLs that reflect the remaining
-     *               TTL
-     *               since the response was received.
-     * @return The number of records that were written.
-     * @throws IOException If an error occurred while writing (typically indicating overflow).
-     */
-    public synchronized int write(MdnsPacketWriter writer, long now) throws IOException {
-        int count = 0;
-        for (MdnsPointerRecord pointerRecord : pointerRecords) {
-            pointerRecord.write(writer, now);
-            ++count;
-        }
-
-        if (serviceRecord != null) {
-            serviceRecord.write(writer, now);
-            ++count;
-        }
-
-        if (textRecord != null) {
-            textRecord.write(writer, now);
-            ++count;
-        }
-
-        if (inet4AddressRecord != null) {
-            inet4AddressRecord.write(writer, now);
-            ++count;
-        }
-
-        if (inet6AddressRecord != null) {
-            inet6AddressRecord.write(writer, now);
-            ++count;
-        }
-
-        return count;
-    }
-}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsResponseDecoder.java b/service/mdns/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
deleted file mode 100644
index 7cf84f6..0000000
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.connectivity.mdns;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.Network;
-import android.os.SystemClock;
-
-import com.android.server.connectivity.mdns.util.MdnsLogger;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.net.DatagramPacket;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
-
-/** A class that decodes mDNS responses from UDP packets. */
-public class MdnsResponseDecoder {
-
-    public static final int SUCCESS = 0;
-    private static final String TAG = "MdnsResponseDecoder";
-    private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
-    private final boolean allowMultipleSrvRecordsPerHost =
-            MdnsConfigs.allowMultipleSrvRecordsPerHost();
-    @Nullable private final String[] serviceType;
-    private final Clock clock;
-
-    /** Constructs a new decoder that will extract responses for the given service type. */
-    public MdnsResponseDecoder(@NonNull Clock clock, @Nullable String[] serviceType) {
-        this.clock = clock;
-        this.serviceType = serviceType;
-    }
-
-    private static void skipMdnsRecord(MdnsPacketReader reader) throws IOException {
-        reader.skip(2 + 4); // skip the class and TTL
-        int dataLength = reader.readUInt16();
-        reader.skip(dataLength);
-    }
-
-    private static MdnsResponse findResponseWithPointer(
-            List<MdnsResponse> responses, String[] pointer) {
-        if (responses != null) {
-            for (MdnsResponse response : responses) {
-                List<MdnsPointerRecord> pointerRecords = response.getPointerRecords();
-                if (pointerRecords == null) {
-                    continue;
-                }
-                for (MdnsPointerRecord pointerRecord : pointerRecords) {
-                    if (Arrays.equals(pointerRecord.getPointer(), pointer)) {
-                        return response;
-                    }
-                }
-            }
-        }
-        return null;
-    }
-
-    private static MdnsResponse findResponseWithHostName(
-            List<MdnsResponse> responses, String[] hostName) {
-        if (responses != null) {
-            for (MdnsResponse response : responses) {
-                MdnsServiceRecord serviceRecord = response.getServiceRecord();
-                if (serviceRecord == null) {
-                    continue;
-                }
-                if (Arrays.equals(serviceRecord.getServiceHost(), hostName)) {
-                    return response;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Decodes all mDNS responses for the desired service type from a packet. The class does not
-     * check
-     * the responses for completeness; the caller should do that.
-     *
-     * @param packet The packet to read from.
-     * @param interfaceIndex the network interface index (or {@link
-     *     MdnsSocket#INTERFACE_INDEX_UNSPECIFIED} if not known) at which the packet was received
-     * @param network the network at which the packet was received, or null if it is unknown.
-     * @return A list of mDNS responses, or null if the packet contained no appropriate responses.
-     */
-    public int decode(@NonNull DatagramPacket packet, @NonNull List<MdnsResponse> responses,
-            int interfaceIndex, @Nullable Network network) {
-        MdnsPacketReader reader = new MdnsPacketReader(packet);
-
-        List<MdnsRecord> records;
-        try {
-            reader.readUInt16(); // transaction ID (not used)
-            int flags = reader.readUInt16();
-            if ((flags & MdnsConstants.FLAGS_RESPONSE_MASK) != MdnsConstants.FLAGS_RESPONSE) {
-                return MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE;
-            }
-
-            int numQuestions = reader.readUInt16();
-            int numAnswers = reader.readUInt16();
-            int numAuthority = reader.readUInt16();
-            int numRecords = reader.readUInt16();
-
-            LOGGER.log(String.format(
-                    "num questions: %d, num answers: %d, num authority: %d, num records: %d",
-                    numQuestions, numAnswers, numAuthority, numRecords));
-
-            if (numAnswers < 1) {
-                return MdnsResponseErrorCode.ERROR_NO_ANSWERS;
-            }
-
-            records = new LinkedList<>();
-
-            for (int i = 0; i < (numAnswers + numAuthority + numRecords); ++i) {
-                String[] name;
-                try {
-                    name = reader.readLabels();
-                } catch (IOException e) {
-                    LOGGER.e("Failed to read labels from mDNS response.", e);
-                    return MdnsResponseErrorCode.ERROR_READING_RECORD_NAME;
-                }
-                int type = reader.readUInt16();
-
-                switch (type) {
-                    case MdnsRecord.TYPE_A: {
-                        try {
-                            records.add(new MdnsInetAddressRecord(name, MdnsRecord.TYPE_A, reader));
-                        } catch (IOException e) {
-                            LOGGER.e("Failed to read A record from mDNS response.", e);
-                            return MdnsResponseErrorCode.ERROR_READING_A_RDATA;
-                        }
-                        break;
-                    }
-
-                    case MdnsRecord.TYPE_AAAA: {
-                        try {
-                            // AAAA should only contain the IPv6 address.
-                            MdnsInetAddressRecord record =
-                                    new MdnsInetAddressRecord(name, MdnsRecord.TYPE_AAAA, reader);
-                            if (record.getInet6Address() != null) {
-                                records.add(record);
-                            }
-                        } catch (IOException e) {
-                            LOGGER.e("Failed to read AAAA record from mDNS response.", e);
-                            return MdnsResponseErrorCode.ERROR_READING_AAAA_RDATA;
-                        }
-                        break;
-                    }
-
-                    case MdnsRecord.TYPE_PTR: {
-                        try {
-                            records.add(new MdnsPointerRecord(name, reader));
-                        } catch (IOException e) {
-                            LOGGER.e("Failed to read PTR record from mDNS response.", e);
-                            return MdnsResponseErrorCode.ERROR_READING_PTR_RDATA;
-                        }
-                        break;
-                    }
-
-                    case MdnsRecord.TYPE_SRV: {
-                        if (name.length == 4) {
-                            try {
-                                records.add(new MdnsServiceRecord(name, reader));
-                            } catch (IOException e) {
-                                LOGGER.e("Failed to read SRV record from mDNS response.", e);
-                                return MdnsResponseErrorCode.ERROR_READING_SRV_RDATA;
-                            }
-                        } else {
-                            try {
-                                skipMdnsRecord(reader);
-                            } catch (IOException e) {
-                                LOGGER.e("Failed to skip SVR record from mDNS response.", e);
-                                return MdnsResponseErrorCode.ERROR_SKIPPING_SRV_RDATA;
-                            }
-                        }
-                        break;
-                    }
-
-                    case MdnsRecord.TYPE_TXT: {
-                        try {
-                            records.add(new MdnsTextRecord(name, reader));
-                        } catch (IOException e) {
-                            LOGGER.e("Failed to read TXT record from mDNS response.", e);
-                            return MdnsResponseErrorCode.ERROR_READING_TXT_RDATA;
-                        }
-                        break;
-                    }
-
-                    default: {
-                        try {
-                            skipMdnsRecord(reader);
-                        } catch (IOException e) {
-                            LOGGER.e("Failed to skip mDNS record.", e);
-                            return MdnsResponseErrorCode.ERROR_SKIPPING_UNKNOWN_RECORD;
-                        }
-                    }
-                }
-            }
-        } catch (EOFException e) {
-            LOGGER.e("Reached the end of the mDNS response unexpectedly.", e);
-            return MdnsResponseErrorCode.ERROR_END_OF_FILE;
-        }
-
-        // The response records are structured in a hierarchy, where some records reference
-        // others, as follows:
-        //
-        //        PTR
-        //        / \
-        //       /   \
-        //      TXT  SRV
-        //           / \
-        //          /   \
-        //         A   AAAA
-        //
-        // But the order in which these records appear in the response packet is completely
-        // arbitrary. This means that we need to rescan the record list to construct each level of
-        // this hierarchy.
-        //
-        // PTR: service type -> service instance name
-        //
-        // SRV: service instance name -> host name (priority, weight)
-        //
-        // TXT: service instance name -> machine readable txt entries.
-        //
-        // A: host name -> IP address
-
-        // Loop 1: find PTR records, which identify distinct service instances.
-        long now = SystemClock.elapsedRealtime();
-        for (MdnsRecord record : records) {
-            if (record instanceof MdnsPointerRecord) {
-                String[] name = record.getName();
-                if ((serviceType == null)
-                        || Arrays.equals(name, serviceType)
-                        || ((name.length == (serviceType.length + 2))
-                        && name[1].equals(MdnsConstants.SUBTYPE_LABEL)
-                        && MdnsRecord.labelsAreSuffix(serviceType, name))) {
-                    MdnsPointerRecord pointerRecord = (MdnsPointerRecord) record;
-                    // Group PTR records that refer to the same service instance name into a single
-                    // response.
-                    MdnsResponse response = findResponseWithPointer(responses,
-                            pointerRecord.getPointer());
-                    if (response == null) {
-                        response = new MdnsResponse(now, interfaceIndex, network);
-                        responses.add(response);
-                    }
-                    // Set interface index earlier because some responses have PTR record only.
-                    // Need to know every response is getting from which interface.
-                    response.addPointerRecord((MdnsPointerRecord) record);
-                }
-            }
-        }
-
-        // Loop 2: find SRV and TXT records, which reference the pointer in the PTR record.
-        for (MdnsRecord record : records) {
-            if (record instanceof MdnsServiceRecord) {
-                MdnsServiceRecord serviceRecord = (MdnsServiceRecord) record;
-                MdnsResponse response = findResponseWithPointer(responses, serviceRecord.getName());
-                if (response != null) {
-                    response.setServiceRecord(serviceRecord);
-                }
-            } else if (record instanceof MdnsTextRecord) {
-                MdnsTextRecord textRecord = (MdnsTextRecord) record;
-                MdnsResponse response = findResponseWithPointer(responses, textRecord.getName());
-                if (response != null) {
-                    response.setTextRecord(textRecord);
-                }
-            }
-        }
-
-        // Loop 3: find A and AAAA records, which reference the host name in the SRV record.
-        for (MdnsRecord record : records) {
-            if (record instanceof MdnsInetAddressRecord) {
-                MdnsInetAddressRecord inetRecord = (MdnsInetAddressRecord) record;
-                if (allowMultipleSrvRecordsPerHost) {
-                    List<MdnsResponse> matchingResponses =
-                            findResponsesWithHostName(responses, inetRecord.getName());
-                    for (MdnsResponse response : matchingResponses) {
-                        assignInetRecord(response, inetRecord);
-                    }
-                } else {
-                    MdnsResponse response =
-                            findResponseWithHostName(responses, inetRecord.getName());
-                    if (response != null) {
-                        assignInetRecord(response, inetRecord);
-                    }
-                }
-            }
-        }
-
-        return SUCCESS;
-    }
-
-    private static void assignInetRecord(MdnsResponse response, MdnsInetAddressRecord inetRecord) {
-        if (inetRecord.getInet4Address() != null) {
-            response.setInet4AddressRecord(inetRecord);
-        } else if (inetRecord.getInet6Address() != null) {
-            response.setInet6AddressRecord(inetRecord);
-        }
-    }
-
-    private static List<MdnsResponse> findResponsesWithHostName(
-            @Nullable List<MdnsResponse> responses, String[] hostName) {
-        if (responses == null || responses.isEmpty()) {
-            return List.of();
-        }
-
-        List<MdnsResponse> result = null;
-        for (MdnsResponse response : responses) {
-            MdnsServiceRecord serviceRecord = response.getServiceRecord();
-            if (serviceRecord == null) {
-                continue;
-            }
-            if (Arrays.equals(serviceRecord.getServiceHost(), hostName)) {
-                if (result == null) {
-                    result = new ArrayList<>(/* initialCapacity= */ responses.size());
-                }
-                result.add(response);
-            }
-        }
-        return result == null ? List.of() : result;
-    }
-
-    public static class Clock {
-        public long elapsedRealtime() {
-            return SystemClock.elapsedRealtime();
-        }
-    }
-}
\ No newline at end of file
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
deleted file mode 100644
index 538f376..0000000
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ /dev/null
@@ -1,470 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.connectivity.mdns;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Pair;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.connectivity.mdns.util.MdnsLogger;
-
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-
-/**
- * Instance of this class sends and receives mDNS packets of a given service type and invoke
- * registered {@link MdnsServiceBrowserListener} instances.
- */
-public class MdnsServiceTypeClient {
-
-    private static final int DEFAULT_MTU = 1500;
-    private static final MdnsLogger LOGGER = new MdnsLogger("MdnsServiceTypeClient");
-
-    private final String serviceType;
-    private final String[] serviceTypeLabels;
-    private final MdnsSocketClient socketClient;
-    private final ScheduledExecutorService executor;
-    private final Object lock = new Object();
-    private final Set<MdnsServiceBrowserListener> listeners = new ArraySet<>();
-    private final Map<String, MdnsResponse> instanceNameToResponse = new HashMap<>();
-    private final boolean removeServiceAfterTtlExpires =
-            MdnsConfigs.removeServiceAfterTtlExpires();
-    private final boolean allowSearchOptionsToRemoveExpiredService =
-            MdnsConfigs.allowSearchOptionsToRemoveExpiredService();
-
-    @Nullable private MdnsSearchOptions searchOptions;
-
-    // The session ID increases when startSendAndReceive() is called where we schedule a
-    // QueryTask for
-    // new subtypes. It stays the same between packets for same subtypes.
-    private long currentSessionId = 0;
-
-    @GuardedBy("lock")
-    @Nullable
-    private Future<?> requestTaskFuture;
-
-    /**
-     * Constructor of {@link MdnsServiceTypeClient}.
-     *
-     * @param socketClient Sends and receives mDNS packet.
-     * @param executor     A {@link ScheduledExecutorService} used to schedule query tasks.
-     */
-    public MdnsServiceTypeClient(
-            @NonNull String serviceType,
-            @NonNull MdnsSocketClient socketClient,
-            @NonNull ScheduledExecutorService executor) {
-        this.serviceType = serviceType;
-        this.socketClient = socketClient;
-        this.executor = executor;
-        serviceTypeLabels = TextUtils.split(serviceType, "\\.");
-    }
-
-    private static MdnsServiceInfo buildMdnsServiceInfoFromResponse(
-            @NonNull MdnsResponse response, @NonNull String[] serviceTypeLabels) {
-        String[] hostName = null;
-        int port = 0;
-        if (response.hasServiceRecord()) {
-            hostName = response.getServiceRecord().getServiceHost();
-            port = response.getServiceRecord().getServicePort();
-        }
-
-        String ipv4Address = null;
-        String ipv6Address = null;
-        if (response.hasInet4AddressRecord()) {
-            Inet4Address inet4Address = response.getInet4AddressRecord().getInet4Address();
-            ipv4Address = (inet4Address == null) ? null : inet4Address.getHostAddress();
-        }
-        if (response.hasInet6AddressRecord()) {
-            Inet6Address inet6Address = response.getInet6AddressRecord().getInet6Address();
-            ipv6Address = (inet6Address == null) ? null : inet6Address.getHostAddress();
-        }
-        String serviceInstanceName = response.getServiceInstanceName();
-        if (serviceInstanceName == null) {
-            throw new IllegalStateException(
-                    "mDNS response must have non-null service instance name");
-        }
-        List<String> textStrings = null;
-        List<MdnsServiceInfo.TextEntry> textEntries = null;
-        if (response.hasTextRecord()) {
-            textStrings = response.getTextRecord().getStrings();
-            textEntries = response.getTextRecord().getEntries();
-        }
-        // TODO: Throw an error message if response doesn't have Inet6 or Inet4 address.
-        return new MdnsServiceInfo(
-                serviceInstanceName,
-                serviceTypeLabels,
-                response.getSubtypes(),
-                hostName,
-                port,
-                ipv4Address,
-                ipv6Address,
-                textStrings,
-                textEntries,
-                response.getInterfaceIndex(),
-                response.getNetwork());
-    }
-
-    /**
-     * Registers {@code listener} for receiving discovery event of mDNS service instances, and
-     * starts
-     * (or continue) to send mDNS queries periodically.
-     *
-     * @param listener      The {@link MdnsServiceBrowserListener} to register.
-     * @param searchOptions {@link MdnsSearchOptions} contains the list of subtypes to discover.
-     */
-    public void startSendAndReceive(
-            @NonNull MdnsServiceBrowserListener listener,
-            @NonNull MdnsSearchOptions searchOptions) {
-        synchronized (lock) {
-            this.searchOptions = searchOptions;
-            if (listeners.add(listener)) {
-                for (MdnsResponse existingResponse : instanceNameToResponse.values()) {
-                    final MdnsServiceInfo info =
-                            buildMdnsServiceInfoFromResponse(existingResponse, serviceTypeLabels);
-                    listener.onServiceNameDiscovered(info);
-                    if (existingResponse.isComplete()) {
-                        listener.onServiceFound(info);
-                    }
-                }
-            }
-            // Cancel the next scheduled periodical task.
-            if (requestTaskFuture != null) {
-                requestTaskFuture.cancel(true);
-            }
-            // Keep tracking the ScheduledFuture for the task so we can cancel it if caller is not
-            // interested anymore.
-            requestTaskFuture =
-                    executor.submit(
-                            new QueryTask(
-                                    new QueryTaskConfig(
-                                            searchOptions.getSubtypes(),
-                                            searchOptions.isPassiveMode(),
-                                            ++currentSessionId)));
-        }
-    }
-
-    /**
-     * Unregisters {@code listener} from receiving discovery event of mDNS service instances.
-     *
-     * @param listener The {@link MdnsServiceBrowserListener} to unregister.
-     * @return {@code true} if no listener is registered with this client after unregistering {@code
-     * listener}. Otherwise returns {@code false}.
-     */
-    public boolean stopSendAndReceive(@NonNull MdnsServiceBrowserListener listener) {
-        synchronized (lock) {
-            listeners.remove(listener);
-            if (listeners.isEmpty() && requestTaskFuture != null) {
-                requestTaskFuture.cancel(true);
-                requestTaskFuture = null;
-            }
-            return listeners.isEmpty();
-        }
-    }
-
-    public String[] getServiceTypeLabels() {
-        return serviceTypeLabels;
-    }
-
-    public synchronized void processResponse(@NonNull MdnsResponse response) {
-        if (shouldRemoveServiceAfterTtlExpires()) {
-            // Because {@link QueryTask} and {@link processResponse} are running in different
-            // threads. We need to synchronize {@link lock} to protect
-            // {@link instanceNameToResponse} won’t be modified at the same time.
-            synchronized (lock) {
-                if (response.isGoodbye()) {
-                    onGoodbyeReceived(response.getServiceInstanceName());
-                } else {
-                    onResponseReceived(response);
-                }
-            }
-        } else {
-            if (response.isGoodbye()) {
-                onGoodbyeReceived(response.getServiceInstanceName());
-            } else {
-                onResponseReceived(response);
-            }
-        }
-    }
-
-    public synchronized void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode) {
-        for (MdnsServiceBrowserListener listener : listeners) {
-            listener.onFailedToParseMdnsResponse(receivedPacketNumber, errorCode);
-        }
-    }
-
-    private void onResponseReceived(@NonNull MdnsResponse response) {
-        MdnsResponse currentResponse;
-        currentResponse = instanceNameToResponse.get(response.getServiceInstanceName());
-
-        boolean newServiceFound = false;
-        boolean existingServiceChanged = false;
-        boolean serviceBecomesComplete = false;
-        if (currentResponse == null) {
-            newServiceFound = true;
-            currentResponse = response;
-            String serviceInstanceName = response.getServiceInstanceName();
-            if (serviceInstanceName != null) {
-                instanceNameToResponse.put(serviceInstanceName, currentResponse);
-            }
-        } else {
-            boolean before = currentResponse.isComplete();
-            existingServiceChanged = currentResponse.mergeRecordsFrom(response);
-            boolean after = currentResponse.isComplete();
-            serviceBecomesComplete = !before && after;
-        }
-        if (!newServiceFound && !existingServiceChanged) {
-            return;
-        }
-        MdnsServiceInfo serviceInfo =
-                buildMdnsServiceInfoFromResponse(currentResponse, serviceTypeLabels);
-
-        for (MdnsServiceBrowserListener listener : listeners) {
-            if (newServiceFound) {
-                listener.onServiceNameDiscovered(serviceInfo);
-            }
-
-            if (currentResponse.isComplete()) {
-                if (newServiceFound || serviceBecomesComplete) {
-                    listener.onServiceFound(serviceInfo);
-                } else {
-                    listener.onServiceUpdated(serviceInfo);
-                }
-            }
-        }
-    }
-
-    private void onGoodbyeReceived(@Nullable String serviceInstanceName) {
-        final MdnsResponse response = instanceNameToResponse.remove(serviceInstanceName);
-        if (response == null) {
-            return;
-        }
-        for (MdnsServiceBrowserListener listener : listeners) {
-            final MdnsServiceInfo serviceInfo =
-                    buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
-            if (response.isComplete()) {
-                listener.onServiceRemoved(serviceInfo);
-            }
-            listener.onServiceNameRemoved(serviceInfo);
-        }
-    }
-
-    private boolean shouldRemoveServiceAfterTtlExpires() {
-        if (removeServiceAfterTtlExpires) {
-            return true;
-        }
-        return allowSearchOptionsToRemoveExpiredService
-                && searchOptions != null
-                && searchOptions.removeExpiredService();
-    }
-
-    @VisibleForTesting
-    MdnsPacketWriter createMdnsPacketWriter() {
-        return new MdnsPacketWriter(DEFAULT_MTU);
-    }
-
-    // A configuration for the PeriodicalQueryTask that contains parameters to build a query packet.
-    // Call to getConfigForNextRun returns a config that can be used to build the next query task.
-    @VisibleForTesting
-    static class QueryTaskConfig {
-
-        private static final int INITIAL_TIME_BETWEEN_BURSTS_MS =
-                (int) MdnsConfigs.initialTimeBetweenBurstsMs();
-        private static final int TIME_BETWEEN_BURSTS_MS = (int) MdnsConfigs.timeBetweenBurstsMs();
-        private static final int QUERIES_PER_BURST = (int) MdnsConfigs.queriesPerBurst();
-        private static final int TIME_BETWEEN_QUERIES_IN_BURST_MS =
-                (int) MdnsConfigs.timeBetweenQueriesInBurstMs();
-        private static final int QUERIES_PER_BURST_PASSIVE_MODE =
-                (int) MdnsConfigs.queriesPerBurstPassive();
-        private static final int UNSIGNED_SHORT_MAX_VALUE = 65536;
-        // The following fields are used by QueryTask so we need to test them.
-        @VisibleForTesting
-        final List<String> subtypes;
-        private final boolean alwaysAskForUnicastResponse =
-                MdnsConfigs.alwaysAskForUnicastResponseInEachBurst();
-        private final boolean usePassiveMode;
-        private final long sessionId;
-        @VisibleForTesting
-        int transactionId;
-        @VisibleForTesting
-        boolean expectUnicastResponse;
-        private int queriesPerBurst;
-        private int timeBetweenBurstsInMs;
-        private int burstCounter;
-        private int timeToRunNextTaskInMs;
-        private boolean isFirstBurst;
-
-        QueryTaskConfig(@NonNull Collection<String> subtypes, boolean usePassiveMode,
-                long sessionId) {
-            this.usePassiveMode = usePassiveMode;
-            this.subtypes = new ArrayList<>(subtypes);
-            this.queriesPerBurst = QUERIES_PER_BURST;
-            this.burstCounter = 0;
-            this.transactionId = 1;
-            this.expectUnicastResponse = true;
-            this.isFirstBurst = true;
-            this.sessionId = sessionId;
-            // Config the scan frequency based on the scan mode.
-            if (this.usePassiveMode) {
-                // In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and then
-                // in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
-                // queries.
-                this.timeBetweenBurstsInMs = TIME_BETWEEN_BURSTS_MS;
-            } else {
-                // In active scan mode, sends a burst of QUERIES_PER_BURST queries,
-                // TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
-                // then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
-                // doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
-                this.timeBetweenBurstsInMs = INITIAL_TIME_BETWEEN_BURSTS_MS;
-            }
-        }
-
-        QueryTaskConfig getConfigForNextRun() {
-            if (++transactionId > UNSIGNED_SHORT_MAX_VALUE) {
-                transactionId = 1;
-            }
-            // Only the first query expects uni-cast response.
-            expectUnicastResponse = false;
-            if (++burstCounter == queriesPerBurst) {
-                burstCounter = 0;
-
-                if (alwaysAskForUnicastResponse) {
-                    expectUnicastResponse = true;
-                }
-                // In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and
-                // then in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
-                // queries.
-                if (isFirstBurst) {
-                    isFirstBurst = false;
-                    if (usePassiveMode) {
-                        queriesPerBurst = QUERIES_PER_BURST_PASSIVE_MODE;
-                    }
-                }
-                // In active scan mode, sends a burst of QUERIES_PER_BURST queries,
-                // TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
-                // then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
-                // doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
-                timeToRunNextTaskInMs = timeBetweenBurstsInMs;
-                if (timeBetweenBurstsInMs < TIME_BETWEEN_BURSTS_MS) {
-                    timeBetweenBurstsInMs = Math.min(timeBetweenBurstsInMs * 2,
-                            TIME_BETWEEN_BURSTS_MS);
-                }
-            } else {
-                timeToRunNextTaskInMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
-            }
-            return this;
-        }
-    }
-
-    // A FutureTask that enqueues a single query, and schedule a new FutureTask for the next task.
-    private class QueryTask implements Runnable {
-
-        private final QueryTaskConfig config;
-
-        QueryTask(@NonNull QueryTaskConfig config) {
-            this.config = config;
-        }
-
-        @Override
-        public void run() {
-            Pair<Integer, List<String>> result;
-            try {
-                result =
-                        new EnqueueMdnsQueryCallable(
-                                socketClient,
-                                createMdnsPacketWriter(),
-                                serviceType,
-                                config.subtypes,
-                                config.expectUnicastResponse,
-                                config.transactionId)
-                                .call();
-            } catch (RuntimeException e) {
-                LOGGER.e(String.format("Failed to run EnqueueMdnsQueryCallable for subtype: %s",
-                        TextUtils.join(",", config.subtypes)), e);
-                result = null;
-            }
-            synchronized (lock) {
-                if (MdnsConfigs.useSessionIdToScheduleMdnsTask()) {
-                    // In case that the task is not canceled successfully, use session ID to check
-                    // if this task should continue to schedule more.
-                    if (config.sessionId != currentSessionId) {
-                        return;
-                    }
-                }
-
-                if (MdnsConfigs.shouldCancelScanTaskWhenFutureIsNull()) {
-                    if (requestTaskFuture == null) {
-                        // If requestTaskFuture is set to null, the task is cancelled. We can't use
-                        // isCancelled() here because this QueryTask is different from the future
-                        // that is returned from executor.schedule(). See b/71646910.
-                        return;
-                    }
-                }
-                if ((result != null)) {
-                    for (MdnsServiceBrowserListener listener : listeners) {
-                        listener.onDiscoveryQuerySent(result.second, result.first);
-                    }
-                }
-                if (shouldRemoveServiceAfterTtlExpires()) {
-                    Iterator<MdnsResponse> iter = instanceNameToResponse.values().iterator();
-                    while (iter.hasNext()) {
-                        MdnsResponse existingResponse = iter.next();
-                        if (existingResponse.hasServiceRecord()
-                                && existingResponse
-                                .getServiceRecord()
-                                .getRemainingTTL(SystemClock.elapsedRealtime())
-                                == 0) {
-                            iter.remove();
-                            for (MdnsServiceBrowserListener listener : listeners) {
-                                String serviceInstanceName =
-                                        existingResponse.getServiceInstanceName();
-                                if (serviceInstanceName != null) {
-                                    final MdnsServiceInfo serviceInfo =
-                                            buildMdnsServiceInfoFromResponse(
-                                                    existingResponse, serviceTypeLabels);
-                                    if (existingResponse.isComplete()) {
-                                        listener.onServiceRemoved(serviceInfo);
-                                    }
-                                    listener.onServiceNameRemoved(serviceInfo);
-                                }
-                            }
-                        }
-                    }
-                }
-                QueryTaskConfig config = this.config.getConfigForNextRun();
-                requestTaskFuture =
-                        executor.schedule(
-                                new QueryTask(config), config.timeToRunNextTaskInMs, MILLISECONDS);
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsSocketProvider.java b/service/mdns/com/android/server/connectivity/mdns/MdnsSocketProvider.java
deleted file mode 100644
index b8c324e..0000000
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsSocketProvider.java
+++ /dev/null
@@ -1,460 +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 com.android.server.connectivity.mdns;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.INetd;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.NetworkRequest;
-import android.net.TetheringManager;
-import android.net.TetheringManager.TetheringEventCallback;
-import android.os.Handler;
-import android.os.Looper;
-import android.system.OsConstants;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
-import com.android.net.module.util.ip.NetlinkMonitor;
-import com.android.net.module.util.netlink.NetlinkConstants;
-import com.android.net.module.util.netlink.NetlinkMessage;
-import com.android.server.connectivity.mdns.util.MdnsLogger;
-
-import java.io.IOException;
-import java.net.InterfaceAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * The {@link MdnsSocketProvider} manages the multiple sockets for mDns.
- *
- * <p>This class is not thread safe, it is intended to be used only from the looper thread.
- * However, the constructor is an exception, as it is called on another thread;
- * therefore for thread safety all members of this class MUST either be final or initialized
- * to their default value (0, false or null).
- *
- */
-public class MdnsSocketProvider {
-    private static final String TAG = MdnsSocketProvider.class.getSimpleName();
-    private static final boolean DBG = MdnsDiscoveryManager.DBG;
-    private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
-    @NonNull private final Context mContext;
-    @NonNull private final Handler mHandler;
-    @NonNull private final Dependencies mDependencies;
-    @NonNull private final NetworkCallback mNetworkCallback;
-    @NonNull private final TetheringEventCallback mTetheringEventCallback;
-    @NonNull private final NetlinkMonitor mNetlinkMonitor;
-    private final ArrayMap<Network, SocketInfo> mNetworkSockets = new ArrayMap<>();
-    private final ArrayMap<String, SocketInfo> mTetherInterfaceSockets = new ArrayMap<>();
-    private final ArrayMap<Network, LinkProperties> mActiveNetworksLinkProperties =
-            new ArrayMap<>();
-    private final ArrayMap<SocketCallback, Network> mCallbacksToRequestedNetworks =
-            new ArrayMap<>();
-    private final List<String> mLocalOnlyInterfaces = new ArrayList<>();
-    private final List<String> mTetheredInterfaces = new ArrayList<>();
-    private boolean mMonitoringSockets = false;
-
-    public MdnsSocketProvider(@NonNull Context context, @NonNull Looper looper) {
-        this(context, looper, new Dependencies());
-    }
-
-    MdnsSocketProvider(@NonNull Context context, @NonNull Looper looper,
-            @NonNull Dependencies deps) {
-        mContext = context;
-        mHandler = new Handler(looper);
-        mDependencies = deps;
-        mNetworkCallback = new NetworkCallback() {
-            @Override
-            public void onLost(Network network) {
-                mActiveNetworksLinkProperties.remove(network);
-                removeSocket(network, null /* interfaceName */);
-            }
-
-            @Override
-            public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
-                handleLinkPropertiesChanged(network, lp);
-            }
-        };
-        mTetheringEventCallback = new TetheringEventCallback() {
-            @Override
-            public void onLocalOnlyInterfacesChanged(@NonNull List<String> interfaces) {
-                handleTetherInterfacesChanged(mLocalOnlyInterfaces, interfaces);
-            }
-
-            @Override
-            public void onTetheredInterfacesChanged(@NonNull List<String> interfaces) {
-                handleTetherInterfacesChanged(mTetheredInterfaces, interfaces);
-            }
-        };
-
-        mNetlinkMonitor = new SocketNetlinkMonitor(mHandler);
-    }
-
-    /**
-     * Dependencies of MdnsSocketProvider, for injection in tests.
-     */
-    @VisibleForTesting
-    public static class Dependencies {
-        /*** Get network interface by given interface name */
-        public NetworkInterfaceWrapper getNetworkInterfaceByName(String interfaceName)
-                throws SocketException {
-            final NetworkInterface ni = NetworkInterface.getByName(interfaceName);
-            return ni == null ? null : new NetworkInterfaceWrapper(ni);
-        }
-
-        /*** Check whether given network interface can support mdns */
-        public boolean canScanOnInterface(NetworkInterfaceWrapper networkInterface) {
-            return MulticastNetworkInterfaceProvider.canScanOnInterface(networkInterface);
-        }
-
-        /*** Create a MdnsInterfaceSocket */
-        public MdnsInterfaceSocket createMdnsInterfaceSocket(NetworkInterface networkInterface,
-                int port) throws IOException {
-            return new MdnsInterfaceSocket(networkInterface, port);
-        }
-    }
-
-    /*** Data class for storing socket related info  */
-    private static class SocketInfo {
-        final MdnsInterfaceSocket mSocket;
-        final List<LinkAddress> mAddresses = new ArrayList<>();
-
-        SocketInfo(MdnsInterfaceSocket socket, List<LinkAddress> addresses) {
-            mSocket = socket;
-            mAddresses.addAll(addresses);
-        }
-    }
-
-    private static class SocketNetlinkMonitor extends NetlinkMonitor {
-        SocketNetlinkMonitor(Handler handler) {
-            super(handler, LOGGER.mLog, TAG, OsConstants.NETLINK_ROUTE,
-                    NetlinkConstants.RTMGRP_IPV4_IFADDR | NetlinkConstants.RTMGRP_IPV6_IFADDR);
-        }
-
-        @Override
-        public void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) {
-            // TODO: Handle netlink message.
-        }
-    }
-
-    private void ensureRunningOnHandlerThread() {
-        if (mHandler.getLooper().getThread() != Thread.currentThread()) {
-            throw new IllegalStateException(
-                    "Not running on Handler thread: " + Thread.currentThread().getName());
-        }
-    }
-
-    /*** Start monitoring sockets by listening callbacks for sockets creation or removal */
-    public void startMonitoringSockets() {
-        ensureRunningOnHandlerThread();
-        if (mMonitoringSockets) {
-            Log.d(TAG, "Already monitoring sockets.");
-            return;
-        }
-        if (DBG) Log.d(TAG, "Start monitoring sockets.");
-        mContext.getSystemService(ConnectivityManager.class).registerNetworkCallback(
-                new NetworkRequest.Builder().clearCapabilities().build(),
-                mNetworkCallback, mHandler);
-
-        final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class);
-        tetheringManager.registerTetheringEventCallback(mHandler::post, mTetheringEventCallback);
-
-        mHandler.post(mNetlinkMonitor::start);
-        mMonitoringSockets = true;
-    }
-
-    /*** Stop monitoring sockets and unregister callbacks */
-    public void stopMonitoringSockets() {
-        ensureRunningOnHandlerThread();
-        if (!mMonitoringSockets) {
-            Log.d(TAG, "Monitoring sockets hasn't been started.");
-            return;
-        }
-        if (DBG) Log.d(TAG, "Stop monitoring sockets.");
-        mContext.getSystemService(ConnectivityManager.class)
-                .unregisterNetworkCallback(mNetworkCallback);
-
-        final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class);
-        tetheringManager.unregisterTetheringEventCallback(mTetheringEventCallback);
-
-        mHandler.post(mNetlinkMonitor::stop);
-        mMonitoringSockets = false;
-    }
-
-    private static boolean isNetworkMatched(@Nullable Network targetNetwork,
-            @NonNull Network currentNetwork) {
-        return targetNetwork == null || targetNetwork.equals(currentNetwork);
-    }
-
-    private boolean matchRequestedNetwork(Network network) {
-        for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) {
-            final Network requestedNetwork =  mCallbacksToRequestedNetworks.valueAt(i);
-            if (isNetworkMatched(requestedNetwork, network)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean hasAllNetworksRequest() {
-        return mCallbacksToRequestedNetworks.containsValue(null);
-    }
-
-    private void handleLinkPropertiesChanged(Network network, LinkProperties lp) {
-        mActiveNetworksLinkProperties.put(network, lp);
-        if (!matchRequestedNetwork(network)) {
-            if (DBG) {
-                Log.d(TAG, "Ignore LinkProperties change. There is no request for the"
-                        + " Network:" + network);
-            }
-            return;
-        }
-
-        final SocketInfo socketInfo = mNetworkSockets.get(network);
-        if (socketInfo == null) {
-            createSocket(network, lp);
-        } else {
-            // Update the addresses of this socket.
-            final List<LinkAddress> addresses = lp.getLinkAddresses();
-            socketInfo.mAddresses.clear();
-            socketInfo.mAddresses.addAll(addresses);
-            // Try to join the group again.
-            socketInfo.mSocket.joinGroup(addresses);
-
-            notifyAddressesChanged(network, lp);
-        }
-    }
-
-    private static LinkProperties createLPForTetheredInterface(String interfaceName) {
-        final LinkProperties linkProperties = new LinkProperties();
-        linkProperties.setInterfaceName(interfaceName);
-        // TODO: Use NetlinkMonitor to update addresses for tethering interfaces.
-        return linkProperties;
-    }
-
-    private void handleTetherInterfacesChanged(List<String> current, List<String> updated) {
-        if (!hasAllNetworksRequest()) {
-            // Currently, the network for tethering can not be requested, so the sockets for
-            // tethering are only created if there is a request for all networks (interfaces).
-            // Therefore, this change can skip if there is no such request.
-            if (DBG) {
-                Log.d(TAG, "Ignore tether interfaces change. There is no request for all"
-                        + " networks.");
-            }
-            return;
-        }
-
-        final CompareResult<String> interfaceDiff = new CompareResult<>(
-                current, updated);
-        for (String name : interfaceDiff.added) {
-            createSocket(new Network(INetd.LOCAL_NET_ID), createLPForTetheredInterface(name));
-        }
-        for (String name : interfaceDiff.removed) {
-            removeSocket(new Network(INetd.LOCAL_NET_ID), name);
-        }
-        current.clear();
-        current.addAll(updated);
-    }
-
-    private static List<LinkAddress> getLinkAddressFromNetworkInterface(
-            NetworkInterfaceWrapper networkInterface) {
-        List<LinkAddress> addresses = new ArrayList<>();
-        for (InterfaceAddress address : networkInterface.getInterfaceAddresses()) {
-            addresses.add(new LinkAddress(address));
-        }
-        return addresses;
-    }
-
-    private void createSocket(Network network, LinkProperties lp) {
-        final String interfaceName = lp.getInterfaceName();
-        if (interfaceName == null) {
-            Log.e(TAG, "Can not create socket with null interface name.");
-            return;
-        }
-
-        try {
-            final NetworkInterfaceWrapper networkInterface =
-                    mDependencies.getNetworkInterfaceByName(interfaceName);
-            if (networkInterface == null || !mDependencies.canScanOnInterface(networkInterface)) {
-                return;
-            }
-
-            if (DBG) {
-                Log.d(TAG, "Create a socket on network:" + network
-                        + " with interfaceName:" + interfaceName);
-            }
-            final MdnsInterfaceSocket socket = mDependencies.createMdnsInterfaceSocket(
-                    networkInterface.getNetworkInterface(), MdnsConstants.MDNS_PORT);
-            final List<LinkAddress> addresses;
-            if (network.netId == INetd.LOCAL_NET_ID) {
-                addresses = getLinkAddressFromNetworkInterface(networkInterface);
-                mTetherInterfaceSockets.put(interfaceName, new SocketInfo(socket, addresses));
-            } else {
-                addresses = lp.getLinkAddresses();
-                mNetworkSockets.put(network, new SocketInfo(socket, addresses));
-            }
-            // Try to join IPv4/IPv6 group.
-            socket.joinGroup(addresses);
-
-            // Notify the listeners which need this socket.
-            notifySocketCreated(network, socket, addresses);
-        } catch (IOException e) {
-            Log.e(TAG, "Create a socket failed with interface=" + interfaceName, e);
-        }
-    }
-
-    private void removeSocket(Network network, String interfaceName) {
-        final SocketInfo socketInfo = network.netId == INetd.LOCAL_NET_ID
-                ? mTetherInterfaceSockets.remove(interfaceName)
-                : mNetworkSockets.remove(network);
-        if (socketInfo == null) return;
-
-        socketInfo.mSocket.destroy();
-        notifyInterfaceDestroyed(network, socketInfo.mSocket);
-    }
-
-    private void notifySocketCreated(Network network, MdnsInterfaceSocket socket,
-            List<LinkAddress> addresses) {
-        for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) {
-            final Network requestedNetwork = mCallbacksToRequestedNetworks.valueAt(i);
-            if (isNetworkMatched(requestedNetwork, network)) {
-                mCallbacksToRequestedNetworks.keyAt(i).onSocketCreated(network, socket, addresses);
-            }
-        }
-    }
-
-    private void notifyInterfaceDestroyed(Network network, MdnsInterfaceSocket socket) {
-        for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) {
-            final Network requestedNetwork = mCallbacksToRequestedNetworks.valueAt(i);
-            if (isNetworkMatched(requestedNetwork, network)) {
-                mCallbacksToRequestedNetworks.keyAt(i).onInterfaceDestroyed(network, socket);
-            }
-        }
-    }
-
-    private void notifyAddressesChanged(Network network, LinkProperties lp) {
-        for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) {
-            final Network requestedNetwork = mCallbacksToRequestedNetworks.valueAt(i);
-            if (isNetworkMatched(requestedNetwork, network)) {
-                mCallbacksToRequestedNetworks.keyAt(i)
-                        .onAddressesChanged(network, lp.getLinkAddresses());
-            }
-        }
-    }
-
-    private void retrieveAndNotifySocketFromNetwork(Network network, SocketCallback cb) {
-        final SocketInfo socketInfo = mNetworkSockets.get(network);
-        if (socketInfo == null) {
-            final LinkProperties lp = mActiveNetworksLinkProperties.get(network);
-            if (lp == null) {
-                // The requested network is not existed. Maybe wait for LinkProperties change later.
-                if (DBG) Log.d(TAG, "There is no LinkProperties for this network:" + network);
-                return;
-            }
-            createSocket(network, lp);
-        } else {
-            // Notify the socket for requested network.
-            cb.onSocketCreated(network, socketInfo.mSocket, socketInfo.mAddresses);
-        }
-    }
-
-    private void retrieveAndNotifySocketFromInterface(String interfaceName, SocketCallback cb) {
-        final SocketInfo socketInfo = mTetherInterfaceSockets.get(interfaceName);
-        if (socketInfo == null) {
-            createSocket(
-                    new Network(INetd.LOCAL_NET_ID), createLPForTetheredInterface(interfaceName));
-        } else {
-            // Notify the socket for requested network.
-            cb.onSocketCreated(
-                    new Network(INetd.LOCAL_NET_ID), socketInfo.mSocket, socketInfo.mAddresses);
-        }
-    }
-
-    /**
-     * Request a socket for given network.
-     *
-     * @param network the required network for a socket. Null means create sockets on all possible
-     *                networks (interfaces).
-     * @param cb the callback to listen the socket creation.
-     */
-    public void requestSocket(@Nullable Network network, @NonNull SocketCallback cb) {
-        ensureRunningOnHandlerThread();
-        mCallbacksToRequestedNetworks.put(cb, network);
-        if (network == null) {
-            // Does not specify a required network, create sockets for all possible
-            // networks (interfaces).
-            for (int i = 0; i < mActiveNetworksLinkProperties.size(); i++) {
-                retrieveAndNotifySocketFromNetwork(mActiveNetworksLinkProperties.keyAt(i), cb);
-            }
-
-            for (String localInterface : mLocalOnlyInterfaces) {
-                retrieveAndNotifySocketFromInterface(localInterface, cb);
-            }
-
-            for (String tetheredInterface : mTetheredInterfaces) {
-                retrieveAndNotifySocketFromInterface(tetheredInterface, cb);
-            }
-        } else {
-            retrieveAndNotifySocketFromNetwork(network, cb);
-        }
-    }
-
-    /*** Unrequest the socket */
-    public void unrequestSocket(@NonNull SocketCallback cb) {
-        ensureRunningOnHandlerThread();
-        mCallbacksToRequestedNetworks.remove(cb);
-        if (hasAllNetworksRequest()) {
-            // Still has a request for all networks (interfaces).
-            return;
-        }
-
-        // Check if remaining requests are matched any of sockets.
-        for (int i = mNetworkSockets.size() - 1; i >= 0; i--) {
-            if (matchRequestedNetwork(mNetworkSockets.keyAt(i))) continue;
-            mNetworkSockets.removeAt(i).mSocket.destroy();
-        }
-
-        // Remove all sockets for tethering interface because these sockets do not have associated
-        // networks, and they should invoke by a request for all networks (interfaces). If there is
-        // no such request, the sockets for tethering interface should be removed.
-        for (int i = mTetherInterfaceSockets.size() - 1; i >= 0; i--) {
-            mTetherInterfaceSockets.removeAt(i).mSocket.destroy();
-        }
-    }
-
-    /*** Callbacks for listening socket changes */
-    public interface SocketCallback {
-        /*** Notify the socket is created */
-        default void onSocketCreated(@NonNull Network network, @NonNull MdnsInterfaceSocket socket,
-                @NonNull List<LinkAddress> addresses) {}
-        /*** Notify the interface is destroyed */
-        default void onInterfaceDestroyed(@NonNull Network network,
-                @NonNull MdnsInterfaceSocket socket) {}
-        /*** Notify the addresses is changed on the network */
-        default void onAddressesChanged(@NonNull Network network,
-                @NonNull List<LinkAddress> addresses) {}
-    }
-}
diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h
index b44d795..cb6c836 100644
--- a/service/native/include/TrafficController.h
+++ b/service/native/include/TrafficController.h
@@ -21,7 +21,7 @@
 
 #include "android-base/thread_annotations.h"
 #include "bpf/BpfMap.h"
-#include "bpf_shared.h"
+#include "netd.h"
 #include "netdutils/DumpWriter.h"
 #include "netdutils/NetlinkListener.h"
 #include "netdutils/StatusOr.h"
diff --git a/service/native/libs/libclat/Android.bp b/service/native/libs/libclat/Android.bp
index 54d40ac..996706e 100644
--- a/service/native/libs/libclat/Android.bp
+++ b/service/native/libs/libclat/Android.bp
@@ -23,6 +23,9 @@
         "clatutils.cpp",
     ],
     stl: "libc++_static",
+    header_libs: [
+        "bpf_headers",
+    ],
     static_libs: [
         "libip_checksum",
     ],
diff --git a/service/native/libs/libclat/clatutils.cpp b/service/native/libs/libclat/clatutils.cpp
index be86612..6c5c9e3 100644
--- a/service/native/libs/libclat/clatutils.cpp
+++ b/service/native/libs/libclat/clatutils.cpp
@@ -25,26 +25,19 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <bpf/BpfClassic.h>
+
 extern "C" {
 #include "checksum.h"
 }
 
-// Sync from external/android-clat/clatd.h
-#define MAXMTU 65536
-#define PACKETLEN (MAXMTU + sizeof(struct tun_pi))
-
-// Sync from system/netd/include/netid_client.h.
-#define MARK_UNSET 0u
-
 namespace android {
 namespace net {
 namespace clat {
 
-bool isIpv4AddressFree(in_addr_t addr) {
-    int s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
-    if (s == -1) {
-        return 0;
-    }
+bool isIpv4AddressFree(const in_addr_t addr) {
+    const int s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+    if (s == -1) return 0;
 
     // Attempt to connect to the address. If the connection succeeds and getsockname returns the
     // same then the address is already assigned to the system and we can't use it.
@@ -54,9 +47,10 @@
             .sin_addr = {addr},
     };
     socklen_t len = sizeof(sin);
-    bool inuse = connect(s, (struct sockaddr*)&sin, sizeof(sin)) == 0 &&
-                 getsockname(s, (struct sockaddr*)&sin, &len) == 0 && (size_t)len >= sizeof(sin) &&
-                 sin.sin_addr.s_addr == addr;
+    const bool inuse = !connect(s, (struct sockaddr*)&sin, sizeof(sin)) &&
+                       !getsockname(s, (struct sockaddr*)&sin, &len) &&
+                       len == (socklen_t)sizeof(sin) &&
+                       sin.sin_addr.s_addr == addr;
 
     close(s);
     return !inuse;
@@ -66,36 +60,30 @@
 //   ip        - the IP address from the configuration file
 //   prefixlen - the length of the prefix from which addresses may be selected.
 //   returns: the IPv4 address, or INADDR_NONE if no addresses were available
-in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen) {
+in_addr_t selectIpv4Address(const in_addr ip, const int16_t prefixlen) {
     return selectIpv4AddressInternal(ip, prefixlen, isIpv4AddressFree);
 }
 
 // Only allow testing to use this function directly. Otherwise call selectIpv4Address(ip, pfxlen)
 // which has applied valid isIpv4AddressFree function pointer.
-in_addr_t selectIpv4AddressInternal(const in_addr ip, int16_t prefixlen,
-                                    isIpv4AddrFreeFn isIpv4AddressFreeFunc) {
+in_addr_t selectIpv4AddressInternal(const in_addr ip, const int16_t prefixlen,
+                                    const isIpv4AddrFreeFn isIpv4AddressFreeFunc) {
     // Impossible! Only test allows to apply fn.
-    if (isIpv4AddressFreeFunc == nullptr) {
-        return INADDR_NONE;
-    }
+    if (isIpv4AddressFreeFunc == nullptr) return INADDR_NONE;
 
     // Don't accept prefixes that are too large because we scan addresses one by one.
-    if (prefixlen < 16 || prefixlen > 32) {
-        return INADDR_NONE;
-    }
+    if (prefixlen < 16 || prefixlen > 32) return INADDR_NONE;
 
     // All these are in host byte order.
-    in_addr_t mask = 0xffffffff >> (32 - prefixlen) << (32 - prefixlen);
-    in_addr_t ipv4 = ntohl(ip.s_addr);
-    in_addr_t first_ipv4 = ipv4;
-    in_addr_t prefix = ipv4 & mask;
+    const uint32_t mask = 0xffffffff >> (32 - prefixlen) << (32 - prefixlen);
+    uint32_t ipv4 = ntohl(ip.s_addr);
+    const uint32_t first_ipv4 = ipv4;
+    const uint32_t prefix = ipv4 & mask;
 
     // Pick the first IPv4 address in the pool, wrapping around if necessary.
     // So, for example, 192.0.0.4 -> 192.0.0.5 -> 192.0.0.6 -> 192.0.0.7 -> 192.0.0.0.
     do {
-        if (isIpv4AddressFreeFunc(htonl(ipv4))) {
-            return htonl(ipv4);
-        }
+        if (isIpv4AddressFreeFunc(htonl(ipv4))) return htonl(ipv4);
         ipv4 = prefix | ((ipv4 + 1) & ~mask);
     } while (ipv4 != first_ipv4);
 
@@ -103,7 +91,7 @@
 }
 
 // Alters the bits in the IPv6 address to make them checksum neutral with v4 and nat64Prefix.
-void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix) {
+void makeChecksumNeutral(in6_addr* const v6, const in_addr v4, const in6_addr& nat64Prefix) {
     // Fill last 8 bytes of IPv6 address with random bits.
     arc4random_buf(&v6->s6_addr[8], 8);
 
@@ -125,33 +113,35 @@
 }
 
 // Picks a random interface ID that is checksum neutral with the IPv4 address and the NAT64 prefix.
-int generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat64Prefix,
-                        in6_addr* v6, uint32_t mark) {
-    int s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+int generateIpv6Address(const char* const iface, const in_addr v4, const in6_addr& nat64Prefix,
+                        in6_addr* const v6, const uint32_t mark) {
+    const int s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
     if (s == -1) return -errno;
 
     // Socket's mark affects routing decisions (network selection)
     // An fwmark is necessary for clat to bypass the VPN during initialization.
-    if ((mark != MARK_UNSET) && setsockopt(s, SOL_SOCKET, SO_MARK, &mark, sizeof(mark))) {
-        int ret = errno;
-        ALOGE("setsockopt(SOL_SOCKET, SO_MARK) failed: %s", strerror(errno));
+    if (setsockopt(s, SOL_SOCKET, SO_MARK, &mark, sizeof(mark))) {
+        const int err = errno;
+        ALOGE("setsockopt(SOL_SOCKET, SO_MARK) failed: %s", strerror(err));
         close(s);
-        return -ret;
+        return -err;
     }
 
-    if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, iface, strlen(iface) + 1) == -1) {
+    if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, iface, strlen(iface) + 1)) {
+        const int err = errno;
+        ALOGE("setsockopt(SOL_SOCKET, SO_BINDTODEVICE, '%s') failed: %s", iface, strerror(err));
         close(s);
-        return -errno;
+        return -err;
     }
 
     sockaddr_in6 sin6 = {.sin6_family = AF_INET6, .sin6_addr = nat64Prefix};
-    if (connect(s, reinterpret_cast<struct sockaddr*>(&sin6), sizeof(sin6)) == -1) {
+    if (connect(s, reinterpret_cast<struct sockaddr*>(&sin6), sizeof(sin6))) {
         close(s);
         return -errno;
     }
 
     socklen_t len = sizeof(sin6);
-    if (getsockname(s, reinterpret_cast<struct sockaddr*>(&sin6), &len) == -1) {
+    if (getsockname(s, reinterpret_cast<struct sockaddr*>(&sin6), &len)) {
         close(s);
         return -errno;
     }
@@ -170,21 +160,22 @@
     return 0;
 }
 
-int detect_mtu(const struct in6_addr* plat_subnet, uint32_t plat_suffix, uint32_t mark) {
+int detect_mtu(const struct in6_addr* const plat_subnet, const uint32_t plat_suffix,
+               const uint32_t mark) {
     // Create an IPv6 UDP socket.
-    int s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+    const int s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
     if (s < 0) {
-        int ret = errno;
-        ALOGE("socket(AF_INET6, SOCK_DGRAM, 0) failed: %s", strerror(errno));
-        return -ret;
+        const int err = errno;
+        ALOGE("socket(AF_INET6, SOCK_DGRAM, 0) failed: %s", strerror(err));
+        return -err;
     }
 
     // Socket's mark affects routing decisions (network selection)
-    if ((mark != MARK_UNSET) && setsockopt(s, SOL_SOCKET, SO_MARK, &mark, sizeof(mark))) {
-        int ret = errno;
-        ALOGE("setsockopt(SOL_SOCKET, SO_MARK) failed: %s", strerror(errno));
+    if (setsockopt(s, SOL_SOCKET, SO_MARK, &mark, sizeof(mark))) {
+        const int err = errno;
+        ALOGE("setsockopt(SOL_SOCKET, SO_MARK) failed: %s", strerror(err));
         close(s);
-        return -ret;
+        return -err;
     }
 
     // Try to connect udp socket to plat_subnet(96 bits):plat_suffix(32 bits)
@@ -194,20 +185,20 @@
     };
     dst.sin6_addr.s6_addr32[3] = plat_suffix;
     if (connect(s, (struct sockaddr*)&dst, sizeof(dst))) {
-        int ret = errno;
-        ALOGE("connect() failed: %s", strerror(errno));
+        const int err = errno;
+        ALOGE("connect() failed: %s", strerror(err));
         close(s);
-        return -ret;
+        return -err;
     }
 
     // Fetch the socket's IPv6 mtu - this is effectively fetching mtu from routing table
     int mtu;
     socklen_t sz_mtu = sizeof(mtu);
     if (getsockopt(s, SOL_IPV6, IPV6_MTU, &mtu, &sz_mtu)) {
-        int ret = errno;
-        ALOGE("getsockopt(SOL_IPV6, IPV6_MTU) failed: %s", strerror(errno));
+        const int err = errno;
+        ALOGE("getsockopt(SOL_IPV6, IPV6_MTU) failed: %s", strerror(err));
         close(s);
-        return -ret;
+        return -err;
     }
     if (sz_mtu != sizeof(mtu)) {
         ALOGE("getsockopt(SOL_IPV6, IPV6_MTU) returned unexpected size: %d", sz_mtu);
@@ -226,34 +217,26 @@
  *   ifindex - index of interface to add the filter to
  * returns: 0 on success, -errno on failure
  */
-int configure_packet_socket(int sock, in6_addr* addr, int ifindex) {
-    uint32_t* ipv6 = addr->s6_addr32;
-
+int configure_packet_socket(const int sock, const in6_addr* const addr, const int ifindex) {
     // clang-format off
     struct sock_filter filter_code[] = {
-    // Load the first four bytes of the IPv6 destination address (starts 24 bytes in).
-    // Compare it against the first four bytes of our IPv6 address, in host byte order (BPF loads
-    // are always in host byte order). If it matches, continue with next instruction (JMP 0). If it
-    // doesn't match, jump ahead to statement that returns 0 (ignore packet). Repeat for the other
-    // three words of the IPv6 address, and if they all match, return PACKETLEN (accept packet).
-        BPF_STMT(BPF_LD  | BPF_W   | BPF_ABS,  24),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    htonl(ipv6[0]), 0, 7),
-        BPF_STMT(BPF_LD  | BPF_W   | BPF_ABS,  28),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    htonl(ipv6[1]), 0, 5),
-        BPF_STMT(BPF_LD  | BPF_W   | BPF_ABS,  32),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    htonl(ipv6[2]), 0, 3),
-        BPF_STMT(BPF_LD  | BPF_W   | BPF_ABS,  36),
-        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,    htonl(ipv6[3]), 0, 1),
-        BPF_STMT(BPF_RET | BPF_K,              PACKETLEN),
-        BPF_STMT(BPF_RET | BPF_K,              0),
+        BPF_LOAD_IPV6_BE32(daddr.s6_addr32[0]),
+        BPF2_REJECT_IF_NOT_EQUAL(ntohl(addr->s6_addr32[0])),
+        BPF_LOAD_IPV6_BE32(daddr.s6_addr32[1]),
+        BPF2_REJECT_IF_NOT_EQUAL(ntohl(addr->s6_addr32[1])),
+        BPF_LOAD_IPV6_BE32(daddr.s6_addr32[2]),
+        BPF2_REJECT_IF_NOT_EQUAL(ntohl(addr->s6_addr32[2])),
+        BPF_LOAD_IPV6_BE32(daddr.s6_addr32[3]),
+        BPF2_REJECT_IF_NOT_EQUAL(ntohl(addr->s6_addr32[3])),
+        BPF_ACCEPT,
     };
     // clang-format on
     struct sock_fprog filter = {sizeof(filter_code) / sizeof(filter_code[0]), filter_code};
 
     if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) {
-        int res = errno;
-        ALOGE("attach packet filter failed: %s", strerror(errno));
-        return -res;
+        const int err = errno;
+        ALOGE("attach packet filter failed: %s", strerror(err));
+        return -err;
     }
 
     struct sockaddr_ll sll = {
@@ -264,9 +247,9 @@
                     PACKET_OTHERHOST,  // The 464xlat IPv6 address is not assigned to the kernel.
     };
     if (bind(sock, (struct sockaddr*)&sll, sizeof(sll))) {
-        int res = errno;
-        ALOGE("binding packet socket: %s", strerror(errno));
-        return -res;
+        const int err = errno;
+        ALOGE("binding packet socket: %s", strerror(err));
+        return -err;
     }
 
     return 0;
diff --git a/service/native/libs/libclat/clatutils_test.cpp b/service/native/libs/libclat/clatutils_test.cpp
index abd4e81..f4f97db 100644
--- a/service/native/libs/libclat/clatutils_test.cpp
+++ b/service/native/libs/libclat/clatutils_test.cpp
@@ -165,7 +165,7 @@
     TunInterface v6Iface;
     ASSERT_EQ(0, v6Iface.init());
 
-    int s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IPV6));
+    const int s = socket(AF_PACKET, SOCK_RAW | SOCK_CLOEXEC, htons(ETH_P_IPV6));
     EXPECT_LE(0, s);
     struct in6_addr addr6;
     EXPECT_EQ(1, inet_pton(AF_INET6, "2001:db8::f00", &addr6));
diff --git a/service/native/libs/libclat/include/libclat/clatutils.h b/service/native/libs/libclat/include/libclat/clatutils.h
index 991b193..6e17e67 100644
--- a/service/native/libs/libclat/include/libclat/clatutils.h
+++ b/service/native/libs/libclat/include/libclat/clatutils.h
@@ -20,17 +20,19 @@
 namespace net {
 namespace clat {
 
-bool isIpv4AddressFree(in_addr_t addr);
-in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen);
-void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix);
-int generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat64Prefix,
-                        in6_addr* v6, uint32_t mark);
-int detect_mtu(const struct in6_addr* plat_subnet, uint32_t plat_suffix, uint32_t mark);
-int configure_packet_socket(int sock, in6_addr* addr, int ifindex);
+bool isIpv4AddressFree(const in_addr_t addr);
+in_addr_t selectIpv4Address(const in_addr ip, const int16_t prefixlen);
+void makeChecksumNeutral(in6_addr* const v6, const in_addr v4, const in6_addr& nat64Prefix);
+int generateIpv6Address(const char* const iface, const in_addr v4, const in6_addr& nat64Prefix,
+                        in6_addr* const v6, const uint32_t mark);
+int detect_mtu(const struct in6_addr* const plat_subnet, const uint32_t plat_suffix,
+               const uint32_t mark);
+int configure_packet_socket(const int sock, const in6_addr* const addr, const int ifindex);
 
 // For testing
-typedef bool (*isIpv4AddrFreeFn)(in_addr_t);
-in_addr_t selectIpv4AddressInternal(const in_addr ip, int16_t prefixlen, isIpv4AddrFreeFn fn);
+typedef bool (*isIpv4AddrFreeFn)(const in_addr_t);
+in_addr_t selectIpv4AddressInternal(const in_addr ip, const int16_t prefixlen,
+                                    const isIpv4AddrFreeFn fn);
 
 }  // namespace clat
 }  // namespace net
diff --git a/service/proguard.flags b/service/proguard.flags
index 864a28b..cf25f05 100644
--- a/service/proguard.flags
+++ b/service/proguard.flags
@@ -7,7 +7,7 @@
     *;
 }
 
--keepclassmembers class com.android.server.**,android.net.**,com.android.networkstack.** {
+-keepclassmembers class android.net.**,com.android.networkstack.** {
     static final % POLICY_*;
     static final % NOTIFY_TYPE_*;
     static final % TRANSPORT_*;
diff --git a/service/src/com/android/metrics/stats.proto b/service/src/com/android/metrics/stats.proto
index 48b8316..006d20a 100644
--- a/service/src/com/android/metrics/stats.proto
+++ b/service/src/com/android/metrics/stats.proto
@@ -284,3 +284,152 @@
     // The latency of selection issued in milli-second
     optional int32 selection_issued_latency_milli = 5;
 }
+
+message NetworkSliceRequestCountSample {
+    // Bitfield representing the network's capability(e.g. NET_CAPABILITY_PRIORITIZE_LATENCY),
+    // defined in packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+    optional int64 slice_id = 1;
+
+    // Bitfield representing the network's enterprise capability identifier
+    // (e.g. NET_ENTERPRISE_ID_1), defined in
+    // packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+    optional int32 enterprise_id = 2;
+
+    // number of request for this slice
+    optional int32 request_count = 3;
+
+    // number of apps with outstanding request(s) for this slice
+    optional int32 distinct_app_count = 4;
+}
+
+message NetworkSliceSessionEnded {
+    // Bitfield representing the network's capability(e.g. NET_CAPABILITY_PRIORITIZE_LATENCY),
+    // defined in packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+    optional int64 slice_id = 1;
+
+    // Bitfield representing the network's enterprise capability identifier
+    // (e.g. NET_ENTERPRISE_ID_1), defined in
+    // packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+    optional int32 enterprise_id = 2;
+
+    // Number of bytes received at the device on this slice id
+    optional int64 rx_bytes = 3;
+
+    // Number of bytes transmitted by the device on this slice id
+    optional int64 tx_bytes = 4;
+
+    // Number of apps that have used this slice
+    optional int32 number_of_apps = 5;
+
+    // How long(in seconds) this slice has been connected
+    optional int32 slice_connection_duration_sec = 6;
+}
+
+message NetworkSliceDailyDataUsageReported {
+    // Bitfield representing the network's capability(e.g. NET_CAPABILITY_PRIORITIZE_LATENCY),
+    // defined in packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+    optional int64 slice_id = 1;
+
+    // Bitfield representing the network's enterprise capability identifier
+    // (e.g. NET_ENTERPRISE_ID_1), defined in
+    // packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+    optional int32 enterprise_id = 2;
+
+    // Number of bytes received at the device on this slice id
+    optional int64 rx_bytes = 3;
+
+    // Number of bytes transmitted by the device on this slice id
+    optional int64 tx_bytes = 4;
+
+    // Number of apps that have used this slice
+    optional int32 number_of_apps = 5;
+
+    // How long(in seconds) this slice has been connected
+    optional int32 slice_connection_duration_sec = 6;
+}
+
+/**
+ *  Logs DailykeepaliveInfoReported
+ *
+ * Logs from: packages/modules/Connectivity/service/src/com/android/
+ *            server/connectivity/AutomaticOnOffKeepaliveTracker.
+ */
+message DailykeepaliveInfoReported{
+    // Daily duration per number of concurrent keepalive
+    optional DurationPerNumOfKeepalive duration_per_num_of_keepalive = 1;
+
+    // Daily keepalive registered/active duration on each list of keepalive session, in
+    // milli-seconds
+    optional KeepaliveLifetimePerCarrier keepalive_lifetime_per_carrier = 2;
+
+    // Daily number of keepalive requests
+    optional int32 keepalive_requests = 3;
+
+    // Daily number of automatic keepalive requests
+    optional int32 automatic_keepalive_requests = 4;
+
+    // Daily number of distinct apps that requested keepalives
+    optional int32 distinct_user_count = 5;
+
+    // Daily distinct apps uid list that requested keepalives
+    repeated int32 uid = 6;
+}
+
+/**
+ * Daily duration per number of concurrent keepalive
+ *
+ * Logs from: packages/modules/Connectivity/service/src/com/android/
+ *            server/connectivity/AutomaticOnOffKeepaliveTracker.
+ */
+message DurationPerNumOfKeepalive {
+    repeated DurationForNumOfKeepalive duration_for_num_of_keepalive = 1;
+}
+
+message DurationForNumOfKeepalive {
+    // The number of concurrent keepalives is in the device
+    optional int32 num_of_keepalive = 1;
+
+    // How many milliseconds the device has keepalive registration number is num_of_keepalive
+    optional int32 keepalive_registered_durations_msec = 2;
+
+    // How many milliseconds the device has keepalive active(not paused) number is num_of_keepalive
+    optional int32 keepalive_active_durations_msec = 3;
+}
+
+/**
+ * Daily keepalive registered/active duration on each list of Keepalive session, in milli-seconds
+ *
+ * Logs from: packages/modules/Connectivity/service/src/com/android/
+ *            server/connectivity/AutomaticOnOffKeepaliveTracker.
+ */
+message KeepaliveLifetimePerCarrier {
+    // The number of network count on each list of carriers
+    repeated KeepaliveLifetimeForCarrier keepalive_lifetime_for_carrier = 1;
+}
+
+/**
+ * Logs the keepalive registered/active duration in milli-seconds and carrier
+ * info(carrier id, transport, keepalive interval).
+ *
+ * Logs from: packages/modules/Connectivity/service/src/com/android/
+ *            server/connectivity/AutomaticOnOffKeepaliveTracker.
+ */
+message KeepaliveLifetimeForCarrier {
+    // The carrier ID for each keepalive, or TelephonyManager.UNKNOWN_CARRIER_ID(-1) if not cell
+    optional int32 carrier_id = 1;
+
+    // The transport types of the underlying network for each keepalive. A network may include
+    // multiple transport types. Each transfer type is represented by a different bit, defined in
+    // packages/modules/Connectivity/framework/src/android/net/NetworkCapabilities.java
+    optional int32 transport_types = 2;
+
+    // The keepalive interval for each keepalive
+    optional int32 intervals_msec = 3;
+
+    // The lifetime of the keepalive registered today
+    optional int32 lifetime_msec = 4;
+
+    // The duration for which the keepalive was active (not suspended)
+    optional int32 active_lifetime_msec = 5;
+}
+
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index b8a8fb4..ec168dd 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -139,7 +139,7 @@
     @VisibleForTesting public static final long OEM_DENY_1_MATCH = (1 << 9);
     @VisibleForTesting public static final long OEM_DENY_2_MATCH = (1 << 10);
     @VisibleForTesting public static final long OEM_DENY_3_MATCH = (1 << 11);
-    // LINT.ThenChange(packages/modules/Connectivity/bpf_progs/bpf_shared.h)
+    // LINT.ThenChange(packages/modules/Connectivity/bpf_progs/netd.h)
 
     private static final List<Pair<Integer, String>> PERMISSION_LIST = Arrays.asList(
             Pair.create(PERMISSION_INTERNET, "PERMISSION_INTERNET"),
@@ -279,9 +279,10 @@
     private static synchronized void ensureInitialized(final Context context) {
         if (sInitialized) return;
         if (sEnableJavaBpfMap == null) {
-            sEnableJavaBpfMap = DeviceConfigUtils.isFeatureEnabled(context,
-                    DeviceConfig.NAMESPACE_TETHERING, BPF_NET_MAPS_ENABLE_JAVA_BPF_MAP,
-                    false /* defaultValue */) || SdkLevel.isAtLeastU();
+            sEnableJavaBpfMap = SdkLevel.isAtLeastU() ||
+                    DeviceConfigUtils.isFeatureEnabled(context,
+                            DeviceConfig.NAMESPACE_TETHERING, BPF_NET_MAPS_ENABLE_JAVA_BPF_MAP,
+                            DeviceConfigUtils.TETHERING_MODULE_NAME, false /* defaultValue */);
         }
         Log.d(TAG, "BpfNetMaps is initialized with sEnableJavaBpfMap=" + sEnableJavaBpfMap);
 
@@ -383,7 +384,6 @@
      * ALLOWLIST means the firewall denies all by default, uids must be explicitly allowed
      * DENYLIST means the firewall allows all by default, uids must be explicitly denyed
      */
-    @VisibleForTesting
     public boolean isFirewallAllowList(final int chain) {
         switch (chain) {
             case FIREWALL_CHAIN_DOZABLE:
@@ -720,6 +720,90 @@
     }
 
     /**
+     * Get firewall rule of specified firewall chain on specified uid.
+     *
+     * @param childChain target chain
+     * @param uid        target uid
+     * @return either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
+     * @throws UnsupportedOperationException if called on pre-T devices.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    public int getUidRule(final int childChain, final int uid) {
+        throwIfPreT("isUidChainEnabled is not available on pre-T devices");
+
+        final long match = getMatchByFirewallChain(childChain);
+        final boolean isAllowList = isFirewallAllowList(childChain);
+        try {
+            final UidOwnerValue uidMatch = sUidOwnerMap.getValue(new S32(uid));
+            final boolean isMatchEnabled = uidMatch != null && (uidMatch.rule & match) != 0;
+            return isMatchEnabled == isAllowList ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY;
+        } catch (ErrnoException e) {
+            throw new ServiceSpecificException(e.errno,
+                    "Unable to get uid rule status: " + Os.strerror(e.errno));
+        }
+    }
+
+    private Set<Integer> getUidsMatchEnabled(final int childChain) throws ErrnoException {
+        final long match = getMatchByFirewallChain(childChain);
+        Set<Integer> uids = new ArraySet<>();
+        synchronized (sUidOwnerMap) {
+            sUidOwnerMap.forEach((uid, val) -> {
+                if (val == null) {
+                    Log.wtf(TAG, "sUidOwnerMap entry was deleted while holding a lock");
+                } else {
+                    if ((val.rule & match) != 0) {
+                        uids.add(uid.val);
+                    }
+                }
+            });
+        }
+        return uids;
+    }
+
+    /**
+     * Get uids that has FIREWALL_RULE_ALLOW on allowlist chain.
+     * Allowlist means the firewall denies all by default, uids must be explicitly allowed.
+     *
+     * Note that uids that has FIREWALL_RULE_DENY on allowlist chain can not be computed from the
+     * bpf map, since all the uids that does not have explicit FIREWALL_RULE_ALLOW rule in bpf map
+     * are determined to have FIREWALL_RULE_DENY.
+     *
+     * @param childChain target chain
+     * @return Set of uids
+     */
+    public Set<Integer> getUidsWithAllowRuleOnAllowListChain(final int childChain)
+            throws ErrnoException {
+        if (!isFirewallAllowList(childChain)) {
+            throw new IllegalArgumentException("getUidsWithAllowRuleOnAllowListChain is called with"
+                    + " denylist chain:" + childChain);
+        }
+        // Corresponding match is enabled for uids that has FIREWALL_RULE_ALLOW on allowlist chain.
+        return getUidsMatchEnabled(childChain);
+    }
+
+    /**
+     * Get uids that has FIREWALL_RULE_DENY on denylist chain.
+     * Denylist means the firewall allows all by default, uids must be explicitly denyed
+     *
+     * Note that uids that has FIREWALL_RULE_ALLOW on denylist chain can not be computed from the
+     * bpf map, since all the uids that does not have explicit FIREWALL_RULE_DENY rule in bpf map
+     * are determined to have the FIREWALL_RULE_ALLOW.
+     *
+     * @param childChain target chain
+     * @return Set of uids
+     */
+    public Set<Integer> getUidsWithDenyRuleOnDenyListChain(final int childChain)
+            throws ErrnoException {
+        if (isFirewallAllowList(childChain)) {
+            throw new IllegalArgumentException("getUidsWithDenyRuleOnDenyListChain is called with"
+                    + " allowlist chain:" + childChain);
+        }
+        // Corresponding match is enabled for uids that has FIREWALL_RULE_DENY on denylist chain.
+        return getUidsMatchEnabled(childChain);
+    }
+
+    /**
      * Add ingress interface filtering rules to a list of UIDs
      *
      * For a given uid, once a filtering rule is added, the kernel will only allow packets from the
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index a44494c..98ad861 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
+import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
 import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.content.pm.PackageManager.FEATURE_WIFI;
@@ -91,13 +92,14 @@
 import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY;
 import static android.os.Process.INVALID_UID;
 import static android.os.Process.VPN_UID;
-import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
 import static android.system.OsConstants.ETH_P_ALL;
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.IPPROTO_UDP;
 
 import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
 import static com.android.net.module.util.NetworkMonitorUtils.isPrivateDnsValidationRequired;
+import static com.android.net.module.util.PermissionUtils.checkAnyPermissionOf;
 import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
 import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission;
 import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr;
@@ -108,10 +110,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TargetApi;
+import android.app.ActivityManager;
+import android.app.ActivityManager.UidFrozenStateChangedCallback;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
+import android.app.compat.CompatChanges;
 import android.app.usage.NetworkStatsManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -120,6 +125,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.net.CaptivePortal;
 import android.net.CaptivePortalData;
@@ -130,7 +136,6 @@
 import android.net.ConnectivityManager.BlockedReason;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.ConnectivityManager.RestrictBackgroundStatus;
-import android.net.ConnectivityResources;
 import android.net.ConnectivitySettingsManager;
 import android.net.DataStallReportParcelable;
 import android.net.DnsResolverServiceManager;
@@ -194,6 +199,7 @@
 import android.net.Uri;
 import android.net.VpnManager;
 import android.net.VpnTransportInfo;
+import android.net.connectivity.ConnectivityCompatChanges;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
 import android.net.netd.aidl.NativeUidRangeConfig;
@@ -239,9 +245,12 @@
 import android.util.LocalLog;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Range;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
+import androidx.annotation.RequiresApi;
+
 import com.android.connectivity.resources.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -262,14 +271,24 @@
 import com.android.net.module.util.PermissionUtils;
 import com.android.net.module.util.TcUtils;
 import com.android.net.module.util.netlink.InetDiagMessage;
+import com.android.networkstack.apishim.BroadcastOptionsShimImpl;
+import com.android.networkstack.apishim.ConstantsShim;
+import com.android.networkstack.apishim.common.BroadcastOptionsShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+import com.android.server.connectivity.ApplicationSelfCertifiedNetworkCapabilities;
 import com.android.server.connectivity.AutodestructReference;
+import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker;
+import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker.AutomaticOnOffKeepalive;
 import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
 import com.android.server.connectivity.ClatCoordinator;
 import com.android.server.connectivity.ConnectivityFlags;
+import com.android.server.connectivity.ConnectivityResources;
 import com.android.server.connectivity.DnsManager;
 import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
 import com.android.server.connectivity.DscpPolicyTracker;
 import com.android.server.connectivity.FullScore;
+import com.android.server.connectivity.InvalidTagException;
+import com.android.server.connectivity.KeepaliveResourceUtil;
 import com.android.server.connectivity.KeepaliveTracker;
 import com.android.server.connectivity.LingerMonitor;
 import com.android.server.connectivity.MockableSystemProperties;
@@ -279,22 +298,29 @@
 import com.android.server.connectivity.NetworkNotificationManager;
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
 import com.android.server.connectivity.NetworkOffer;
+import com.android.server.connectivity.NetworkPreferenceList;
 import com.android.server.connectivity.NetworkRanker;
 import com.android.server.connectivity.PermissionMonitor;
-import com.android.server.connectivity.ProfileNetworkPreferenceList;
+import com.android.server.connectivity.ProfileNetworkPreferenceInfo;
 import com.android.server.connectivity.ProxyTracker;
 import com.android.server.connectivity.QosCallbackTracker;
 import com.android.server.connectivity.UidRangeUtils;
+import com.android.server.connectivity.VpnNetworkPreferenceInfo;
+import com.android.server.connectivity.wear.CompanionDeviceManagerProxyService;
 
 import libcore.io.IoUtils;
 
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.InterruptedIOException;
 import java.io.PrintWriter;
 import java.io.Writer;
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
+import java.net.SocketException;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -369,6 +395,10 @@
     private static final int DEFAULT_LINGER_DELAY_MS = 30_000;
     private static final int DEFAULT_NASCENT_DELAY_MS = 5_000;
 
+    // Delimiter used when creating the broadcast delivery group for sending
+    // CONNECTIVITY_ACTION broadcast.
+    private static final char DELIVERY_GROUP_KEY_DELIMITER = ';';
+
     // The maximum value for the blocking validation result, in milliseconds.
     public static final int MAX_VALIDATION_IGNORE_AFTER_ROAM_TIME_MS = 10000;
 
@@ -394,8 +424,7 @@
 
     private final MockableSystemProperties mSystemProperties;
 
-    @VisibleForTesting
-    protected final PermissionMonitor mPermissionMonitor;
+    private final PermissionMonitor mPermissionMonitor;
 
     @VisibleForTesting
     final RequestInfoPerUidCounter mNetworkRequestCounter;
@@ -434,13 +463,18 @@
      */
     @GuardedBy("mTNSLock")
     private TestNetworkService mTNS;
+    private final CompanionDeviceManagerProxyService mCdmps;
 
     private final Object mTNSLock = new Object();
 
     private String mCurrentTcpBufferSizes;
 
     private static final SparseArray<String> sMagicDecoderRing = MessageUtils.findMessageNames(
-            new Class[] { ConnectivityService.class, NetworkAgent.class, NetworkAgentInfo.class });
+            new Class[] {
+                    ConnectivityService.class,
+                    NetworkAgent.class,
+                    NetworkAgentInfo.class,
+                    AutomaticOnOffKeepaliveTracker.class });
 
     private enum ReapUnvalidatedNetworks {
         // Tear down networks that have no chance (e.g. even if validated) of becoming
@@ -744,6 +778,22 @@
     private static final int EVENT_USER_DOES_NOT_WANT = 58;
 
     /**
+     * Event to set VPN as preferred network for specific apps.
+     * obj = VpnNetworkPreferenceInfo
+     */
+    private static final int EVENT_SET_VPN_NETWORK_PREFERENCE = 59;
+
+    /**
+     * Event to use low TCP polling timer used in automatic on/off keepalive temporarily.
+     */
+    private static final int EVENT_SET_LOW_TCP_POLLING_UNTIL = 60;
+
+    /**
+     * Event to inform the ConnectivityService handler when a uid has been frozen or unfrozen.
+     */
+    private static final int EVENT_UID_FROZEN_STATE_CHANGED = 61;
+
+    /**
      * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
      * should be shown.
      */
@@ -761,6 +811,12 @@
     private static final long MAX_TEST_ALLOW_BAD_WIFI_UNTIL_MS = 5 * 60 * 1000L;
 
     /**
+     * The maximum alive time to decrease TCP polling timer in automatic on/off keepalive for
+     * testing.
+     */
+    private static final long MAX_TEST_LOW_TCP_POLLING_UNTIL_MS = 5 * 60 * 1000L;
+
+    /**
      * The priority of the tc police rate limiter -- smaller value is higher priority.
      * This value needs to be coordinated with PRIO_CLAT, PRIO_TETHER4, and PRIO_TETHER6.
      */
@@ -823,7 +879,7 @@
 
     private final LocationPermissionChecker mLocationPermissionChecker;
 
-    private final KeepaliveTracker mKeepaliveTracker;
+    private final AutomaticOnOffKeepaliveTracker mKeepaliveTracker;
     private final QosCallbackTracker mQosCallbackTracker;
     private final NetworkNotificationManager mNotifier;
     private final LingerMonitor mLingerMonitor;
@@ -869,6 +925,13 @@
     // Only the handler thread is allowed to access this field.
     private long mIngressRateLimit = -1;
 
+    // This is the cache for the packageName -> ApplicationSelfCertifiedNetworkCapabilities. This
+    // value can be accessed from both handler thread and any random binder thread. Therefore,
+    // accessing this value requires holding a lock. The cache is the same across all the users.
+    @GuardedBy("mSelfCertifiedCapabilityCache")
+    private final Map<String, ApplicationSelfCertifiedNetworkCapabilities>
+            mSelfCertifiedCapabilityCache = new HashMap<>();
+
     /**
      * Implements support for the legacy "one network per network type" model.
      *
@@ -1215,6 +1278,18 @@
             return Binder.getCallingUid();
         }
 
+        public boolean isAtLeastS() {
+            return SdkLevel.isAtLeastS();
+        }
+
+        public boolean isAtLeastT() {
+            return SdkLevel.isAtLeastT();
+        }
+
+        public boolean isAtLeastU() {
+            return SdkLevel.isAtLeastU();
+        }
+
         /**
          * Get system properties to use in ConnectivityService.
          */
@@ -1283,6 +1358,14 @@
         }
 
         /**
+         * @see AutomaticOnOffKeepaliveTracker
+         */
+        public AutomaticOnOffKeepaliveTracker makeAutomaticOnOffKeepaliveTracker(
+                @NonNull Context c, @NonNull Handler h) {
+            return new AutomaticOnOffKeepaliveTracker(c, h);
+        }
+
+        /**
          * @see BatteryStatsManager
          */
         public void reportNetworkInterfaceForTransports(Context context, String iface,
@@ -1319,7 +1402,7 @@
         @Nullable
         public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
                 @NonNull final Context context, @NonNull final TelephonyManager tm) {
-            if (SdkLevel.isAtLeastT()) {
+            if (isAtLeastT()) {
                 return new CarrierPrivilegeAuthenticator(context, tm);
             } else {
                 return null;
@@ -1329,9 +1412,9 @@
         /**
          * @see DeviceConfigUtils#isFeatureEnabled
          */
-        public boolean isFeatureEnabled(Context context, String name, boolean defaultEnabled) {
-            return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name,
-                    TETHERING_MODULE_NAME, defaultEnabled);
+        public boolean isFeatureEnabled(Context context, String name) {
+            return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_TETHERING, name,
+                    TETHERING_MODULE_NAME, false /* defaultValue */);
         }
 
         /**
@@ -1401,6 +1484,77 @@
                         + ", ingress=true, PRIO_POLICE, ETH_P_ALL) failure: ", e);
             }
         }
+
+        /**
+         * Wraps {@link BroadcastOptionsShimImpl#newInstance(BroadcastOptions)}
+         */
+        // TODO: when available in all active branches:
+        //  @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+        @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
+        public BroadcastOptionsShim makeBroadcastOptionsShim(BroadcastOptions options) {
+            return BroadcastOptionsShimImpl.newInstance(options);
+        }
+
+        /**
+         * Wrapper method for
+         * {@link android.app.compat.CompatChanges#isChangeEnabled(long, String, UserHandle)}.
+         *
+         * @param changeId    The ID of the compatibility change in question.
+         * @param packageName The package name of the app in question.
+         * @param user        The user that the operation is done for.
+         * @return {@code true} if the change is enabled for the specified package.
+         */
+        public boolean isChangeEnabled(long changeId, @NonNull final String packageName,
+                @NonNull final UserHandle user) {
+            return CompatChanges.isChangeEnabled(changeId, packageName, user);
+        }
+
+        /**
+         * As above but with a UID.
+         * @see CompatChanges#isChangeEnabled(long, int)
+         */
+        public boolean isChangeEnabled(final long changeId, final int uid) {
+            return CompatChanges.isChangeEnabled(changeId, uid);
+        }
+
+        /**
+         * Call {@link InetDiagMessage#destroyLiveTcpSockets(Set, Set)}
+         *
+         * @param ranges target uid ranges
+         * @param exemptUids uids to skip close socket
+         */
+        public void destroyLiveTcpSockets(@NonNull final Set<Range<Integer>> ranges,
+                @NonNull final Set<Integer> exemptUids)
+                throws SocketException, InterruptedIOException, ErrnoException {
+            InetDiagMessage.destroyLiveTcpSockets(ranges, exemptUids);
+        }
+
+        /**
+         * Call {@link InetDiagMessage#destroyLiveTcpSocketsByOwnerUids(Set)}
+         *
+         * @param ownerUids target uids to close sockets
+         */
+        public void destroyLiveTcpSocketsByOwnerUids(final Set<Integer> ownerUids)
+                throws SocketException, InterruptedIOException, ErrnoException {
+            InetDiagMessage.destroyLiveTcpSocketsByOwnerUids(ownerUids);
+        }
+
+        /**
+         * Schedule the evaluation timeout.
+         *
+         * When a network connects, it's "not evaluated" yet. Detection events cause the network
+         * to be "evaluated" (typically, validation or detection of a captive portal). If none
+         * of these events happen, this time will run out, after which the network is considered
+         * "evaluated" even if nothing happened to it. Notionally that means the system gave up
+         * on this network and considers it won't provide connectivity. In particular, that means
+         * it's when the system prefers it to cell if it's wifi and configuration says it should
+         * prefer bad wifi to cell.
+         */
+        public void scheduleEvaluationTimeout(@NonNull Handler handler,
+                @NonNull final Network network, final long delayMs) {
+            handler.sendMessageDelayed(
+                    handler.obtainMessage(EVENT_INITIAL_EVALUATION_TIMEOUT, network), delayMs);
+        }
     }
 
     public ConnectivityService(Context context) {
@@ -1535,7 +1689,7 @@
         mSettingsObserver = new SettingsObserver(mContext, mHandler);
         registerSettingsCallbacks();
 
-        mKeepaliveTracker = new KeepaliveTracker(mContext, mHandler);
+        mKeepaliveTracker = mDeps.makeAutomaticOnOffKeepaliveTracker(mContext, mHandler);
         mNotifier = new NetworkNotificationManager(mContext, mTelephonyManager);
         mQosCallbackTracker = new QosCallbackTracker(mHandler, mNetworkRequestCounter);
 
@@ -1576,7 +1730,7 @@
             // Even if it could, running on S would at least require mocking out the BPF map,
             // otherwise the unit tests will fail on pre-T devices where the seccomp filter blocks
             // the bpf syscall. http://aosp/1907693
-            if (SdkLevel.isAtLeastT()) {
+            if (mDeps.isAtLeastT()) {
                 mDscpPolicyTracker = new DscpPolicyTracker();
             }
         } catch (ErrnoException e) {
@@ -1585,6 +1739,38 @@
 
         mIngressRateLimit = ConnectivitySettingsManager.getIngressRateLimitInBytesPerSecond(
                 mContext);
+
+        if (mDeps.isAtLeastT()) {
+            mCdmps = new CompanionDeviceManagerProxyService(context);
+        } else {
+            mCdmps = null;
+        }
+
+        if (mDeps.isAtLeastU()
+                && mDeps.isFeatureEnabled(context, KEY_DESTROY_FROZEN_SOCKETS_VERSION)) {
+            final UidFrozenStateChangedCallback frozenStateChangedCallback =
+                    new UidFrozenStateChangedCallback() {
+                @Override
+                public void onUidFrozenStateChanged(int[] uids, int[] frozenStates) {
+                    if (uids.length != frozenStates.length) {
+                        Log.wtf(TAG, "uids has length " + uids.length
+                                + " but frozenStates has length " + frozenStates.length);
+                        return;
+                    }
+
+                    final UidFrozenStateChangedArgs args =
+                            new UidFrozenStateChangedArgs(uids, frozenStates);
+
+                    mHandler.sendMessage(
+                            mHandler.obtainMessage(EVENT_UID_FROZEN_STATE_CHANGED, args));
+                }
+            };
+
+            final ActivityManager activityManager =
+                    mContext.getSystemService(ActivityManager.class);
+            activityManager.registerUidFrozenStateChangedCallback(
+                    (Runnable r) -> r.run(), frozenStateChangedCallback);
+        }
     }
 
     /**
@@ -1616,6 +1802,17 @@
                 TYPE_NONE, NetworkRequest.Type.REQUEST);
     }
 
+    private NetworkRequest createVpnRequest() {
+        final NetworkCapabilities netCap = new NetworkCapabilities.Builder()
+                .withoutDefaultCapabilities()
+                .addTransportType(TRANSPORT_VPN)
+                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+                .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                .build();
+        netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName());
+        return createNetworkRequest(NetworkRequest.Type.REQUEST, netCap);
+    }
+
     private NetworkRequest createDefaultInternetRequestForTransport(
             int transportType, NetworkRequest.Type type) {
         final NetworkCapabilities netCap = new NetworkCapabilities();
@@ -2228,11 +2425,12 @@
         if (newNc.getNetworkSpecifier() != null) {
             newNc.setNetworkSpecifier(newNc.getNetworkSpecifier().redact());
         }
-        if (!checkAnyPermissionOf(callerPid, callerUid, android.Manifest.permission.NETWORK_STACK,
+        if (!checkAnyPermissionOf(mContext, callerPid, callerUid,
+                android.Manifest.permission.NETWORK_STACK,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)) {
             newNc.setAdministratorUids(new int[0]);
         }
-        if (!checkAnyPermissionOf(
+        if (!checkAnyPermissionOf(mContext,
                 callerPid, callerUid, android.Manifest.permission.NETWORK_FACTORY)) {
             newNc.setAllowedUids(new ArraySet<>());
             newNc.setSubscriptionIds(Collections.emptySet());
@@ -2501,7 +2699,8 @@
         final ArrayList<NetworkStateSnapshot> result = new ArrayList<>();
         for (Network network : getAllNetworks()) {
             final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
-            if (nai != null && nai.everConnected()) {
+            final boolean includeNetwork = (nai != null) && nai.isCreated();
+            if (includeNetwork) {
                 // TODO (b/73321673) : NetworkStateSnapshot contains a copy of the
                 // NetworkCapabilities, which may contain UIDs of apps to which the
                 // network applies. Should the UIDs be cleared so as not to leak or
@@ -2741,15 +2940,39 @@
         setUidBlockedReasons(uid, blockedReasons);
     }
 
-    private boolean checkAnyPermissionOf(int pid, int uid, String... permissions) {
-        for (String permission : permissions) {
-            if (mContext.checkPermission(permission, pid, uid) == PERMISSION_GRANTED) {
-                return true;
+    static final class UidFrozenStateChangedArgs {
+        final int[] mUids;
+        final int[] mFrozenStates;
+
+        UidFrozenStateChangedArgs(int[] uids, int[] frozenStates) {
+            mUids = uids;
+            mFrozenStates = frozenStates;
+        }
+    }
+
+    private void handleFrozenUids(int[] uids, int[] frozenStates) {
+        final ArraySet<Range<Integer>> ranges = new ArraySet<>();
+
+        for (int i = 0; i < uids.length; i++) {
+            if (frozenStates[i] == UID_FROZEN_STATE_FROZEN) {
+                Integer uidAsInteger = Integer.valueOf(uids[i]);
+                ranges.add(new Range(uidAsInteger, uidAsInteger));
             }
         }
-        return false;
+
+        if (!ranges.isEmpty()) {
+            final Set<Integer> exemptUids = new ArraySet<>();
+            try {
+                mDeps.destroyLiveTcpSockets(ranges, exemptUids);
+            } catch (Exception e) {
+                loge("Exception in socket destroy: " + e);
+            }
+        }
     }
 
+    @VisibleForTesting
+    static final String KEY_DESTROY_FROZEN_SOCKETS_VERSION = "destroy_frozen_sockets_version";
+
     private void enforceInternetPermission() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.INTERNET,
@@ -2814,9 +3037,10 @@
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
     }
 
-    private void enforceSettingsOrUseRestrictedNetworksPermission() {
+    private void enforceSettingsOrSetupWizardOrUseRestrictedNetworksPermission() {
         enforceAnyPermissionOf(mContext,
                 android.Manifest.permission.NETWORK_SETTINGS,
+                android.Manifest.permission.NETWORK_SETUP_WIZARD,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
                 Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS);
     }
@@ -2907,13 +3131,13 @@
     }
 
     private boolean checkNetworkStackPermission(int pid, int uid) {
-        return checkAnyPermissionOf(pid, uid,
+        return checkAnyPermissionOf(mContext, pid, uid,
                 android.Manifest.permission.NETWORK_STACK,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
     }
 
     private boolean checkNetworkSignalStrengthWakeupPermission(int pid, int uid) {
-        return checkAnyPermissionOf(pid, uid,
+        return checkAnyPermissionOf(mContext, pid, uid,
                 android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
                 android.Manifest.permission.NETWORK_SETTINGS);
@@ -3010,6 +3234,7 @@
                         ConnectivityManager.EXTRA_NETWORK_INFO);
                 final BroadcastOptions opts = BroadcastOptions.makeBasic();
                 opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.M);
+                applyMostRecentPolicyForConnectivityAction(opts, ni);
                 options = opts.toBundle();
                 intent.addFlags(Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
             }
@@ -3021,6 +3246,33 @@
         }
     }
 
+    private void applyMostRecentPolicyForConnectivityAction(BroadcastOptions options,
+            NetworkInfo info) {
+        // Delivery group policy APIs are only available on U+.
+        if (!mDeps.isAtLeastU()) return;
+
+        final BroadcastOptionsShim optsShim = mDeps.makeBroadcastOptionsShim(options);
+        try {
+            // This allows us to discard older broadcasts still waiting to be delivered
+            // which have the same namespace and key.
+            optsShim.setDeliveryGroupPolicy(ConstantsShim.DELIVERY_GROUP_POLICY_MOST_RECENT);
+            optsShim.setDeliveryGroupMatchingKey(ConnectivityManager.CONNECTIVITY_ACTION,
+                    createDeliveryGroupKeyForConnectivityAction(info));
+            optsShim.setDeferralPolicy(ConstantsShim.DEFERRAL_POLICY_UNTIL_ACTIVE);
+        } catch (UnsupportedApiLevelException e) {
+            Log.wtf(TAG, "Using unsupported API" + e);
+        }
+    }
+
+    @VisibleForTesting
+    static String createDeliveryGroupKeyForConnectivityAction(NetworkInfo info) {
+        final StringBuilder sb = new StringBuilder();
+        sb.append(info.getType()).append(DELIVERY_GROUP_KEY_DELIMITER);
+        sb.append(info.getSubtype()).append(DELIVERY_GROUP_KEY_DELIMITER);
+        sb.append(info.getExtraInfo());
+        return sb.toString();
+    }
+
     /**
      * Called by SystemServer through ConnectivityManager when the system is ready.
      */
@@ -3075,7 +3327,7 @@
         }
 
         // On T+ devices, register callback for statsd to pull NETWORK_BPF_MAP_INFO atom
-        if (SdkLevel.isAtLeastT()) {
+        if (mDeps.isAtLeastT()) {
             mBpfNetMaps.setPullAtomCallback(mContext);
         }
         // Wait PermissionMonitor to finish the permission update. Then MultipathPolicyTracker won't
@@ -3116,22 +3368,23 @@
     private void updateMtu(@NonNull LinkProperties newLp, @Nullable LinkProperties oldLp) {
         final String iface = newLp.getInterfaceName();
         final int mtu = newLp.getMtu();
-        if (oldLp == null && mtu == 0) {
+        if (mtu == 0) {
             // Silently ignore unset MTU value.
             return;
         }
-        if (oldLp != null && newLp.isIdenticalMtu(oldLp)) {
-            if (VDBG) log("identical MTU - not setting");
+        if (oldLp != null && newLp.isIdenticalMtu(oldLp)
+                && TextUtils.equals(oldLp.getInterfaceName(), iface)) {
+            if (VDBG) log("identical MTU and iface - not setting");
             return;
         }
-        if (!LinkProperties.isValidMtu(mtu, newLp.hasGlobalIpv6Address())) {
-            if (mtu != 0) loge("Unexpected mtu value: " + mtu + ", " + iface);
+        // Cannot set MTU without interface name
+        if (TextUtils.isEmpty(iface)) {
+            if (VDBG) log("Setting MTU size with null iface.");
             return;
         }
 
-        // Cannot set MTU without interface name
-        if (TextUtils.isEmpty(iface)) {
-            loge("Setting MTU size with null iface.");
+        if (!LinkProperties.isValidMtu(mtu, newLp.hasGlobalIpv6Address())) {
+            loge("Unexpected mtu value: " + mtu + ", " + iface);
             return;
         }
 
@@ -3413,7 +3666,7 @@
         if (!mProfileNetworkPreferences.isEmpty()) {
             pw.println("Profile preferences:");
             pw.increaseIndent();
-            pw.println(mProfileNetworkPreferences.preferences);
+            pw.println(mProfileNetworkPreferences);
             pw.decreaseIndent();
         }
         if (!mOemNetworkPreferences.isEmpty()) {
@@ -3671,9 +3924,9 @@
                     break;
                 }
                 case NetworkAgent.EVENT_UNREGISTER_AFTER_REPLACEMENT: {
-                    if (!nai.isCreated()) {
-                        Log.d(TAG, "unregisterAfterReplacement on uncreated " + nai.toShortString()
-                                + ", tearing down instead");
+                    if (!nai.everConnected()) {
+                        Log.d(TAG, "unregisterAfterReplacement on never-connected "
+                                + nai.toShortString() + ", tearing down instead");
                         teardownUnneededNetwork(nai);
                         break;
                     }
@@ -4258,6 +4511,25 @@
         }
     }
 
+    @VisibleForTesting
+    protected static boolean shouldCreateNetworksImmediately() {
+        // Before U, physical networks are only created when the agent advances to CONNECTED.
+        // In U and above, all networks are immediately created when the agent is registered.
+        return SdkLevel.isAtLeastU();
+    }
+
+    private static boolean shouldCreateNativeNetwork(@NonNull NetworkAgentInfo nai,
+            @NonNull NetworkInfo.State state) {
+        if (nai.isCreated()) return false;
+        if (state == NetworkInfo.State.CONNECTED) return true;
+        if (state != NetworkInfo.State.CONNECTING) {
+            // TODO: throw if no WTFs are observed in the field.
+            Log.wtf(TAG, "Uncreated network in invalid state: " + state);
+            return false;
+        }
+        return nai.isVPN() || shouldCreateNetworksImmediately();
+    }
+
     private static boolean shouldDestroyNativeNetwork(@NonNull NetworkAgentInfo nai) {
         return nai.isCreated() && !nai.isDestroyed();
     }
@@ -4265,7 +4537,7 @@
     @VisibleForTesting
     boolean shouldIgnoreValidationFailureAfterRoam(NetworkAgentInfo nai) {
         // T+ devices should use unregisterAfterReplacement.
-        if (SdkLevel.isAtLeastT()) return false;
+        if (mDeps.isAtLeastT()) return false;
 
         // If the network never roamed, return false. The check below is not sufficient if time
         // since boot is less than blockTimeOut, though that's extremely unlikely to happen.
@@ -4308,7 +4580,7 @@
         // because they lost all their requests or because their score isn't good)
         // then they would disconnect organically, report their new state and then
         // disconnect the channel.
-        if (nai.networkInfo.isConnected()) {
+        if (nai.networkInfo.isConnected() || nai.networkInfo.isSuspended()) {
             nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,
                     null, null);
         }
@@ -4327,7 +4599,7 @@
         mQosCallbackTracker.handleNetworkReleased(nai.network);
         for (String iface : nai.linkProperties.getAllInterfaceNames()) {
             // Disable wakeup packet monitoring for each interface.
-            wakeupModifyInterface(iface, nai.networkCapabilities, false);
+            wakeupModifyInterface(iface, nai, false);
         }
         nai.networkMonitor().notifyNetworkDisconnected();
         mNetworkAgentInfos.remove(nai);
@@ -4338,6 +4610,9 @@
             mNetworkForNetId.remove(nai.network.getNetId());
         }
         propagateUnderlyingNetworkCapabilities(nai.network);
+        // Update allowed network lists in netd. This should be called after removing nai
+        // from mNetworkAgentInfos.
+        updateProfileAllowedNetworks();
         // Remove all previously satisfied requests.
         for (int i = 0; i < nai.numNetworkRequests(); i++) {
             final NetworkRequest request = nai.requestAt(i);
@@ -4401,7 +4676,7 @@
             // for an unnecessarily long time.
             destroyNativeNetwork(nai);
         }
-        if (!nai.isCreated() && !SdkLevel.isAtLeastT()) {
+        if (!nai.isCreated() && !mDeps.isAtLeastT()) {
             // Backwards compatibility: send onNetworkDestroyed even if network was never created.
             // This can never run if the code above runs because shouldDestroyNativeNetwork is
             // false if the network was never created.
@@ -4485,7 +4760,7 @@
     }
 
     private void checkNrisConsistency(final NetworkRequestInfo nri) {
-        if (SdkLevel.isAtLeastT()) {
+        if (mDeps.isAtLeastT()) {
             for (final NetworkRequestInfo n : mNetworkRequests.values()) {
                 if (n.mBinder != null && n.mBinder == nri.mBinder) {
                     // Temporary help to debug b/194394697 ; TODO : remove this function when the
@@ -4772,6 +5047,7 @@
                 }
             }
         }
+
         nri.mPerUidCounter.decrementCount(nri.mUid);
         mNetworkRequestInfoLogs.log("RELEASE " + nri);
         checkNrisConsistency(nri);
@@ -4876,7 +5152,7 @@
     }
 
     private RequestInfoPerUidCounter getRequestCounter(NetworkRequestInfo nri) {
-        return checkAnyPermissionOf(
+        return checkAnyPermissionOf(mContext,
                 nri.mPid, nri.mUid, NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
                 ? mSystemNetworkRequestCounter : mNetworkRequestCounter;
     }
@@ -4917,6 +5193,19 @@
                 mHandler.obtainMessage(EVENT_SET_TEST_ALLOW_BAD_WIFI_UNTIL, timeMs));
     }
 
+    @Override
+    public void setTestLowTcpPollingTimerForKeepalive(long timeMs) {
+        enforceSettingsPermission();
+
+        if (timeMs > System.currentTimeMillis() + MAX_TEST_LOW_TCP_POLLING_UNTIL_MS) {
+            throw new IllegalArgumentException("Argument should not exceed "
+                    + MAX_TEST_LOW_TCP_POLLING_UNTIL_MS + "ms from now");
+        }
+
+        mHandler.sendMessage(
+                mHandler.obtainMessage(EVENT_SET_LOW_TCP_POLLING_UNTIL, timeMs));
+    }
+
     private void handleSetAcceptUnvalidated(Network network, boolean accept, boolean always) {
         if (DBG) log("handleSetAcceptUnvalidated network=" + network +
                 " accept=" + accept + " always=" + always);
@@ -5018,8 +5307,7 @@
     /** Schedule evaluation timeout */
     @VisibleForTesting
     public void scheduleEvaluationTimeout(@NonNull final Network network, final long delayMs) {
-        mHandler.sendMessageDelayed(
-                mHandler.obtainMessage(EVENT_INITIAL_EVALUATION_TIMEOUT, network), delayMs);
+        mDeps.scheduleEvaluationTimeout(mHandler, network, delayMs);
     }
 
     @Override
@@ -5460,17 +5748,51 @@
                     handleConfigureAlwaysOnNetworks();
                     break;
                 }
-                // Sent by KeepaliveTracker to process an app request on the state machine thread.
-                case NetworkAgent.CMD_START_SOCKET_KEEPALIVE: {
+                // Sent by AutomaticOnOffKeepaliveTracker to process an app request on the
+                // handler thread.
+                case AutomaticOnOffKeepaliveTracker.CMD_REQUEST_START_KEEPALIVE: {
                     mKeepaliveTracker.handleStartKeepalive(msg);
                     break;
                 }
+                case AutomaticOnOffKeepaliveTracker.CMD_MONITOR_AUTOMATIC_KEEPALIVE: {
+                    final AutomaticOnOffKeepalive ki =
+                            mKeepaliveTracker.getKeepaliveForBinder((IBinder) msg.obj);
+                    if (null == ki) return; // The callback was unregistered before the alarm fired
+
+                    final Network underpinnedNetwork = ki.getUnderpinnedNetwork();
+                    final Network network = ki.getNetwork();
+                    boolean networkFound = false;
+                    boolean underpinnedNetworkFound = false;
+                    for (NetworkAgentInfo n : mNetworkAgentInfos) {
+                        if (n.network.equals(network)) networkFound = true;
+                        if (n.everConnected() && n.network.equals(underpinnedNetwork)) {
+                            underpinnedNetworkFound = true;
+                        }
+                    }
+
+                    // If the network no longer exists, then the keepalive should have been
+                    // cleaned up already. There is no point trying to resume keepalives.
+                    if (!networkFound) return;
+
+                    if (underpinnedNetworkFound) {
+                        mKeepaliveTracker.handleMonitorAutomaticKeepalive(ki,
+                                underpinnedNetwork.netId);
+                    } else {
+                        // If no underpinned network, then make sure the keepalive is running.
+                        mKeepaliveTracker.handleMaybeResumeKeepalive(ki);
+                    }
+                    break;
+                }
                 // Sent by KeepaliveTracker to process an app request on the state machine thread.
                 case NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE: {
-                    NetworkAgentInfo nai = getNetworkAgentInfoForNetwork((Network) msg.obj);
-                    int slot = msg.arg1;
-                    int reason = msg.arg2;
-                    mKeepaliveTracker.handleStopKeepalive(nai, slot, reason);
+                    final AutomaticOnOffKeepalive ki = mKeepaliveTracker.getKeepaliveForBinder(
+                            (IBinder) msg.obj);
+                    if (ki == null) {
+                        Log.e(TAG, "Attempt to stop an already stopped keepalive");
+                        return;
+                    }
+                    final int reason = msg.arg2;
+                    mKeepaliveTracker.handleStopKeepalive(ki, reason);
                     break;
                 }
                 case EVENT_REPORT_NETWORK_CONNECTIVITY: {
@@ -5498,10 +5820,8 @@
                     break;
                 }
                 case EVENT_SET_PROFILE_NETWORK_PREFERENCE: {
-                    final Pair<List<ProfileNetworkPreferenceList.Preference>,
-                            IOnCompleteListener> arg =
-                            (Pair<List<ProfileNetworkPreferenceList.Preference>,
-                                    IOnCompleteListener>) msg.obj;
+                    final Pair<List<ProfileNetworkPreferenceInfo>, IOnCompleteListener> arg =
+                            (Pair<List<ProfileNetworkPreferenceInfo>, IOnCompleteListener>) msg.obj;
                     handleSetProfileNetworkPreference(arg.first, arg.second);
                     break;
                 }
@@ -5524,6 +5844,18 @@
                     nai.onPreventAutomaticReconnect();
                     nai.disconnect();
                     break;
+                case EVENT_SET_VPN_NETWORK_PREFERENCE:
+                    handleSetVpnNetworkPreference((VpnNetworkPreferenceInfo) msg.obj);
+                    break;
+                case EVENT_SET_LOW_TCP_POLLING_UNTIL: {
+                    final long time = ((Long) msg.obj).longValue();
+                    mKeepaliveTracker.handleSetTestLowTcpPollingTimer(time);
+                    break;
+                }
+                case EVENT_UID_FROZEN_STATE_CHANGED:
+                    UidFrozenStateChangedArgs args = (UidFrozenStateChangedArgs) msg.obj;
+                    handleFrozenUids(args.mUids, args.mFrozenStates);
+                    break;
             }
         }
     }
@@ -6018,7 +6350,7 @@
                     + Arrays.toString(ranges) + "): netd command failed: " + e);
         }
 
-        if (SdkLevel.isAtLeastT()) {
+        if (mDeps.isAtLeastT()) {
             mPermissionMonitor.updateVpnLockdownUidRanges(requireVpn, ranges);
         }
 
@@ -6137,12 +6469,14 @@
         if (mOemNetworkPreferences.getNetworkPreferences().size() > 0) {
             handleSetOemNetworkPreference(mOemNetworkPreferences, null);
         }
+        updateProfileAllowedNetworks();
     }
 
     private void onUserRemoved(@NonNull final UserHandle user) {
         // If there was a network preference for this user, remove it.
         handleSetProfileNetworkPreference(
-                List.of(new ProfileNetworkPreferenceList.Preference(user, null, true)),
+                List.of(new ProfileNetworkPreferenceInfo(user, null, true,
+                        false /* blockingNonEnterprise */)),
                 null /* listener */);
         if (mOemNetworkPreferences.getNetworkPreferences().size() > 0) {
             handleSetOemNetworkPreference(mOemNetworkPreferences, null);
@@ -6158,6 +6492,11 @@
         if (isMappedInOemNetworkPreference(packageName)) {
             handleSetOemNetworkPreference(mOemNetworkPreferences, null);
         }
+
+        // Invalidates cache entry when the package is updated.
+        synchronized (mSelfCertifiedCapabilityCache) {
+            mSelfCertifiedCapabilityCache.remove(packageName);
+        }
     }
 
     private final BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
@@ -6494,8 +6833,6 @@
 
         @Override
         public void binderDied() {
-            log("ConnectivityService NetworkRequestInfo binderDied(" +
-                    "uid/pid:" + mUid + "/" + mPid + ", " + mRequests + ", " + mBinder + ")");
             // As an immutable collection, mRequests cannot change by the time the
             // lambda is evaluated on the handler thread so calling .get() from a binder thread
             // is acceptable. Use handleReleaseNetworkRequest and not directly
@@ -6688,7 +7025,7 @@
                 enforceAccessPermission();
                 break;
             case TRACK_SYSTEM_DEFAULT:
-                enforceSettingsOrUseRestrictedNetworksPermission();
+                enforceSettingsOrSetupWizardOrUseRestrictedNetworksPermission();
                 networkCapabilities = new NetworkCapabilities(defaultNc);
                 break;
             case BACKGROUND_REQUEST:
@@ -6786,8 +7123,72 @@
                 asUid, requests, nr, msgr, binder, callbackFlags, callingAttributionTag);
     }
 
+    private boolean shouldCheckCapabilitiesDeclaration(
+            @NonNull final NetworkCapabilities networkCapabilities, final int callingUid,
+            @NonNull final String callingPackageName) {
+        final UserHandle user = UserHandle.getUserHandleForUid(callingUid);
+        // Only run the check if the change is enabled.
+        if (!mDeps.isChangeEnabled(
+                ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION,
+                callingPackageName, user)) {
+            return false;
+        }
+
+        return networkCapabilities.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                || networkCapabilities.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY);
+    }
+
+    private void enforceRequestCapabilitiesDeclaration(@NonNull final String callerPackageName,
+            @NonNull final NetworkCapabilities networkCapabilities) {
+        // This check is added to fix the linter error for "current min is 30", which is not going
+        // to happen because Connectivity service always run in S+.
+        if (!mDeps.isAtLeastS()) {
+            Log.wtf(TAG, "Connectivity service should always run in at least SDK S");
+            return;
+        }
+        ApplicationSelfCertifiedNetworkCapabilities applicationNetworkCapabilities;
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSelfCertifiedCapabilityCache) {
+                applicationNetworkCapabilities = mSelfCertifiedCapabilityCache.get(
+                        callerPackageName);
+                if (applicationNetworkCapabilities == null) {
+                    final PackageManager packageManager = mContext.getPackageManager();
+                    final PackageManager.Property networkSliceProperty = packageManager.getProperty(
+                            ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+                            callerPackageName
+                    );
+                    final XmlResourceParser parser = packageManager
+                            .getResourcesForApplication(callerPackageName)
+                            .getXml(networkSliceProperty.getResourceId());
+                    applicationNetworkCapabilities =
+                            ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser);
+                    mSelfCertifiedCapabilityCache.put(callerPackageName,
+                            applicationNetworkCapabilities);
+                }
+
+            }
+        } catch (PackageManager.NameNotFoundException ne) {
+            throw new SecurityException(
+                    "Cannot find " + ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES
+                            + " property");
+        } catch (XmlPullParserException | IOException | InvalidTagException e) {
+            throw new SecurityException(e.getMessage());
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        applicationNetworkCapabilities.enforceSelfCertifiedNetworkCapabilitiesDeclared(
+                networkCapabilities);
+    }
     private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
             String callingPackageName, String callingAttributionTag, final int callingUid) {
+        if (shouldCheckCapabilitiesDeclaration(networkCapabilities, callingUid,
+                callingPackageName)) {
+            enforceRequestCapabilitiesDeclaration(callingPackageName, networkCapabilities);
+        }
         if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
             // For T+ devices, callers with carrier privilege could request with CBS capabilities.
             if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
@@ -7112,8 +7513,14 @@
     // Current per-profile network preferences. This object follows the same threading rules as
     // the OEM network preferences above.
     @NonNull
-    private ProfileNetworkPreferenceList mProfileNetworkPreferences =
-            new ProfileNetworkPreferenceList();
+    private NetworkPreferenceList<UserHandle, ProfileNetworkPreferenceInfo>
+            mProfileNetworkPreferences = new NetworkPreferenceList<>();
+
+    // Current VPN network preferences. This object follows the same threading rules as the OEM
+    // network preferences above.
+    @NonNull
+    private NetworkPreferenceList<String, VpnNetworkPreferenceInfo>
+            mVpnNetworkPreferences = new NetworkPreferenceList<>();
 
     // A set of UIDs that should use mobile data preferentially if available. This object follows
     // the same threading rules as the OEM network preferences above.
@@ -7539,7 +7946,7 @@
         // the LinkProperties for the network are accurate.
         networkAgent.clatd.fixupLinkProperties(oldLp, newLp);
 
-        updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities);
+        updateInterfaces(newLp, oldLp, netId, networkAgent);
 
         // update filtering rules, need to happen after the interface update so netd knows about the
         // new interface (the interface name -> index map becomes initialized)
@@ -7564,7 +7971,7 @@
 
         if (isDefaultNetwork(networkAgent)) {
             handleApplyDefaultProxy(newLp.getHttpProxy());
-        } else {
+        } else if (networkAgent.everConnected()) {
             updateProxy(newLp, oldLp);
         }
 
@@ -7598,6 +8005,10 @@
         mKeepaliveTracker.handleCheckKeepalivesStillValid(networkAgent);
     }
 
+    private void applyInitialLinkProperties(@NonNull NetworkAgentInfo nai) {
+        updateLinkProperties(nai, new LinkProperties(nai.linkProperties), null);
+    }
+
     /**
      * @param naData captive portal data from NetworkAgent
      * @param apiData captive portal data from capport API
@@ -7647,10 +8058,27 @@
         return captivePortalBuilder.build();
     }
 
-    private void wakeupModifyInterface(String iface, NetworkCapabilities caps, boolean add) {
+    @VisibleForTesting
+    static String makeNflogPrefix(String iface, long networkHandle) {
+        // This needs to be kept in sync and backwards compatible with the decoding logic in
+        // NetdEventListenerService, which is non-mainline code.
+        return SdkLevel.isAtLeastU() ? (networkHandle + ":" + iface) : ("iface:" + iface);
+    }
+
+    private static boolean isWakeupMarkingSupported(NetworkCapabilities capabilities) {
+        if (capabilities.hasTransport(TRANSPORT_WIFI)) {
+            return true;
+        }
+        if (SdkLevel.isAtLeastU() && capabilities.hasTransport(TRANSPORT_CELLULAR)) {
+            return true;
+        }
+        return false;
+    }
+
+    private void wakeupModifyInterface(String iface, NetworkAgentInfo nai, boolean add) {
         // Marks are only available on WiFi interfaces. Checking for
         // marks on unsupported interfaces is harmless.
-        if (!caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
+        if (!isWakeupMarkingSupported(nai.networkCapabilities)) {
             return;
         }
 
@@ -7663,7 +8091,7 @@
             return;
         }
 
-        final String prefix = "iface:" + iface;
+        final String prefix = makeNflogPrefix(iface, nai.network.getNetworkHandle());
         try {
             if (add) {
                 mNetd.wakeupAddInterface(iface, prefix, mark, mask);
@@ -7673,12 +8101,11 @@
         } catch (Exception e) {
             loge("Exception modifying wakeup packet monitoring: " + e);
         }
-
     }
 
     private void updateInterfaces(final @NonNull LinkProperties newLp,
             final @Nullable LinkProperties oldLp, final int netId,
-            final @NonNull NetworkCapabilities caps) {
+            final @NonNull NetworkAgentInfo nai) {
         final CompareResult<String> interfaceDiff = new CompareResult<>(
                 oldLp != null ? oldLp.getAllInterfaceNames() : null, newLp.getAllInterfaceNames());
         if (!interfaceDiff.added.isEmpty()) {
@@ -7686,9 +8113,9 @@
                 try {
                     if (DBG) log("Adding iface " + iface + " to network " + netId);
                     mNetd.networkAddInterface(netId, iface);
-                    wakeupModifyInterface(iface, caps, true);
+                    wakeupModifyInterface(iface, nai, true);
                     mDeps.reportNetworkInterfaceForTransports(mContext, iface,
-                            caps.getTransportTypes());
+                            nai.networkCapabilities.getTransportTypes());
                 } catch (Exception e) {
                     logw("Exception adding interface: " + e);
                 }
@@ -7697,7 +8124,7 @@
         for (final String iface : interfaceDiff.removed) {
             try {
                 if (DBG) log("Removing iface " + iface + " from network " + netId);
-                wakeupModifyInterface(iface, caps, false);
+                wakeupModifyInterface(iface, nai, false);
                 mNetd.networkRemoveInterface(netId, iface);
             } catch (Exception e) {
                 loge("Exception removing interface: " + e);
@@ -8154,7 +8581,7 @@
         // On T and above, allow rules are needed for all VPNs. Allow rule with null iface is a
         // wildcard to allow apps to receive packets on all interfaces. This is required to accept
         // incoming traffic in Lockdown mode by overriding the Lockdown blocking rule.
-        return SdkLevel.isAtLeastT() && nai.isVPN() && lp != null && lp.getInterfaceName() != null;
+        return mDeps.isAtLeastT() && nai.isVPN() && lp != null && lp.getInterfaceName() != null;
     }
 
     private static UidRangeParcel[] toUidRangeStableParcels(final @NonNull Set<UidRange> ranges) {
@@ -8186,11 +8613,11 @@
         return stableRanges;
     }
 
-    private void maybeCloseSockets(NetworkAgentInfo nai, UidRangeParcel[] ranges,
-            int[] exemptUids) {
+    private void maybeCloseSockets(NetworkAgentInfo nai, Set<UidRange> ranges,
+            Set<Integer> exemptUids) {
         if (nai.isVPN() && !nai.networkAgentConfig.allowBypass) {
             try {
-                mNetd.socketDestroy(ranges, exemptUids);
+                mDeps.destroyLiveTcpSockets(UidRange.toIntRanges(ranges), exemptUids);
             } catch (Exception e) {
                 loge("Exception in socket destroy: ", e);
             }
@@ -8198,15 +8625,16 @@
     }
 
     private void updateVpnUidRanges(boolean add, NetworkAgentInfo nai, Set<UidRange> uidRanges) {
-        int[] exemptUids = new int[2];
+        final Set<Integer> exemptUids = new ArraySet<>();
         // TODO: Excluding VPN_UID is necessary in order to not to kill the TCP connection used
         // by PPTP. Fix this by making Vpn set the owner UID to VPN_UID instead of system when
         // starting a legacy VPN, and remove VPN_UID here. (b/176542831)
-        exemptUids[0] = VPN_UID;
-        exemptUids[1] = nai.networkCapabilities.getOwnerUid();
+        exemptUids.add(VPN_UID);
+        exemptUids.add(nai.networkCapabilities.getOwnerUid());
         UidRangeParcel[] ranges = toUidRangeStableParcels(uidRanges);
 
-        maybeCloseSockets(nai, ranges, exemptUids);
+        // Close sockets before modifying uid ranges so that RST packets can reach to the server.
+        maybeCloseSockets(nai, uidRanges, exemptUids);
         try {
             if (add) {
                 mNetd.networkAddUidRangesParcel(new NativeUidRangeConfig(
@@ -8219,7 +8647,8 @@
             loge("Exception while " + (add ? "adding" : "removing") + " uid ranges " + uidRanges +
                     " on netId " + nai.network.netId + ". " + e);
         }
-        maybeCloseSockets(nai, ranges, exemptUids);
+        // Close sockets that established connection while requesting netd.
+        maybeCloseSockets(nai, uidRanges, exemptUids);
     }
 
     private boolean isProxySetOnAnyDefaultNetwork() {
@@ -8400,14 +8829,14 @@
         try {
             if (DBG) log("Sending " + pendingIntent);
             final BroadcastOptions options = BroadcastOptions.makeBasic();
-            if (SdkLevel.isAtLeastT()) {
+            if (mDeps.isAtLeastT()) {
                 // Explicitly disallow the receiver from starting activities, to prevent apps from
                 // utilizing the PendingIntent as a backdoor to do this.
                 options.setPendingIntentBackgroundActivityLaunchAllowed(false);
             }
             pendingIntent.send(mContext, 0, intent, this /* onFinished */, null /* Handler */,
                     null /* requiredPermission */,
-                    SdkLevel.isAtLeastT() ? options.toBundle() : null);
+                    mDeps.isAtLeastT() ? options.toBundle() : null);
         } catch (PendingIntent.CanceledException e) {
             if (DBG) log(pendingIntent + " was not sent, it had been canceled.");
             mPendingIntentWakeLock.release();
@@ -8653,6 +9082,78 @@
         }
     }
 
+    /**
+     * Collect restricted uid ranges for the given network and UserHandle, these uids
+     * are not restricted for matched enterprise networks but being restricted for non-matched
+     * enterprise networks and non-enterprise networks.
+     */
+    @NonNull
+    private ArraySet<UidRange> getRestrictedUidRangesForEnterpriseBlocking(
+            @NonNull NetworkAgentInfo nai, @NonNull UserHandle user) {
+        final ArraySet<UidRange> restrictedUidRanges = new ArraySet<>();
+        for (final ProfileNetworkPreferenceInfo pref : mProfileNetworkPreferences) {
+            if (!pref.user.equals(user) || !pref.blockingNonEnterprise) continue;
+
+            if (nai.networkCapabilities.hasCapability(NET_CAPABILITY_ENTERPRISE)) {
+                // The NC is built from a `ProfileNetworkPreference` which has only one
+                // enterprise ID, so it's guaranteed to have exactly one.
+                final int prefId = pref.capabilities.getEnterpriseIds()[0];
+                if (nai.networkCapabilities.hasEnterpriseId(prefId)) {
+                    continue;
+                }
+            }
+
+            if (UidRangeUtils.doesRangeSetOverlap(restrictedUidRanges,
+                    pref.capabilities.getUidRanges())) {
+                throw new IllegalArgumentException(
+                        "Overlapping uid range in preference: " + pref);
+            }
+            restrictedUidRanges.addAll(pref.capabilities.getUidRanges());
+        }
+        return restrictedUidRanges;
+    }
+
+    private void updateProfileAllowedNetworks() {
+        // Netd command is not implemented before U.
+        if (!mDeps.isAtLeastU()) return;
+
+        ensureRunningOnConnectivityServiceThread();
+        final ArrayList<NativeUidRangeConfig> configs = new ArrayList<>();
+        final List<UserHandle> users = mContext.getSystemService(UserManager.class)
+                        .getUserHandles(true /* excludeDying */);
+        if (users.isEmpty()) {
+            throw new IllegalStateException("No user is available");
+        }
+
+        for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+            ArraySet<UidRange> allowedUidRanges = new ArraySet<>();
+            for (final UserHandle user : users) {
+                final ArraySet<UidRange> restrictedUidRanges =
+                        getRestrictedUidRangesForEnterpriseBlocking(nai, user);
+                allowedUidRanges.addAll(UidRangeUtils.removeRangeSetFromUidRange(
+                        UidRange.createForUser(user), restrictedUidRanges));
+            }
+
+            final UidRangeParcel[] rangesParcel = toUidRangeStableParcels(allowedUidRanges);
+            configs.add(new NativeUidRangeConfig(
+                    nai.network.netId, rangesParcel, 0 /* subPriority */));
+        }
+
+        // The netd API replaces the previous configs with the current configs.
+        // Thus, for network disconnection or preference removal, no need to
+        // unset previous config. Instead, collecting all currently needed
+        // configs and issue to netd.
+        try {
+            mNetd.setNetworkAllowlist(configs.toArray(new NativeUidRangeConfig[0]));
+        } catch (ServiceSpecificException e) {
+            // Has the interface disappeared since the network was built?
+            Log.wtf(TAG, "Unexpected ServiceSpecificException", e);
+        } catch (RemoteException e) {
+            // Netd died. This will cause a runtime restart anyway.
+            Log.wtf(TAG, "Unexpected RemoteException", e);
+        }
+    }
+
     private void makeDefaultNetwork(@Nullable final NetworkAgentInfo newDefaultNetwork) {
         try {
             if (null != newDefaultNetwork) {
@@ -9268,23 +9769,35 @@
                     + oldInfo.getState() + " to " + state);
         }
 
-        if (!networkAgent.isCreated()
-                && (state == NetworkInfo.State.CONNECTED
-                || (state == NetworkInfo.State.CONNECTING && networkAgent.isVPN()))) {
-
+        if (shouldCreateNativeNetwork(networkAgent, state)) {
             // A network that has just connected has zero requests and is thus a foreground network.
             networkAgent.networkCapabilities.addCapability(NET_CAPABILITY_FOREGROUND);
 
             if (!createNativeNetwork(networkAgent)) return;
+
+            networkAgent.setCreated();
+
+            // If the network is created immediately on register, then apply the LinkProperties now.
+            // Otherwise, this is done further down when the network goes into connected state.
+            // Applying the LinkProperties means that the network is ready to carry traffic -
+            // interfaces and routing rules have been added, DNS servers programmed, etc.
+            // For VPNs, this must be done before the capabilities are updated, because as soon as
+            // that happens, UIDs are routed to the network.
+            if (shouldCreateNetworksImmediately()) {
+                applyInitialLinkProperties(networkAgent);
+            }
+
+            // TODO: should this move earlier? It doesn't seem to have anything to do with whether
+            // a network is created or not.
             if (networkAgent.propagateUnderlyingCapabilities()) {
                 // Initialize the network's capabilities to their starting values according to the
                 // underlying networks. This ensures that the capabilities are correct before
                 // anything happens to the network.
                 updateCapabilitiesForNetwork(networkAgent);
             }
-            networkAgent.setCreated();
             networkAgent.onNetworkCreated();
             updateAllowedUids(networkAgent, null, networkAgent.networkCapabilities);
+            updateProfileAllowedNetworks();
         }
 
         if (!networkAgent.everConnected() && state == NetworkInfo.State.CONNECTED) {
@@ -9295,8 +9808,19 @@
             networkAgent.getAndSetNetworkCapabilities(networkAgent.networkCapabilities);
 
             handlePerNetworkPrivateDnsConfig(networkAgent, mDnsManager.getPrivateDnsConfig());
-            updateLinkProperties(networkAgent, new LinkProperties(networkAgent.linkProperties),
-                    null);
+            if (!shouldCreateNetworksImmediately()) {
+                applyInitialLinkProperties(networkAgent);
+            } else {
+                // The network was created when the agent registered, and the LinkProperties are
+                // already up-to-date. However, updateLinkProperties also makes some changes only
+                // when the network connects. Apply those changes here. On T and below these are
+                // handled by the applyInitialLinkProperties call just above.
+                // TODO: stop relying on updateLinkProperties(..., null) to do this.
+                // If something depends on both LinkProperties and connected state, it should be in
+                // this method as well.
+                networkAgent.clatd.update();
+                updateProxy(networkAgent.linkProperties, null);
+            }
 
             // If a rate limit has been configured and is applicable to this network (network
             // provides internet connectivity), apply it. The tc police filter cannot be attached
@@ -9327,13 +9851,13 @@
             // in the absence of a satisfactory, scalable solution which follows an easy/standard
             // process to check the interface version, just use an SDK check. NetworkStack will
             // always be new enough when running on T+.
-            if (SdkLevel.isAtLeastT()) {
+            if (mDeps.isAtLeastT()) {
                 networkAgent.networkMonitor().notifyNetworkConnected(params);
             } else {
                 networkAgent.networkMonitor().notifyNetworkConnected(params.linkProperties,
                         params.networkCapabilities);
             }
-            final long delay = activelyPreferBadWifi()
+            final long delay = !avoidBadWifi() && activelyPreferBadWifi()
                     ? ACTIVELY_PREFER_BAD_WIFI_INITIAL_TIMEOUT_MS
                     : DONT_ACTIVELY_PREFER_BAD_WIFI_INITIAL_TIMEOUT_MS;
             scheduleEvaluationTimeout(networkAgent.network, delay);
@@ -9630,20 +10154,24 @@
         enforceKeepalivePermission();
         mKeepaliveTracker.startNattKeepalive(
                 getNetworkAgentInfoForNetwork(network), null /* fd */,
-                intervalSeconds, cb,
-                srcAddr, srcPort, dstAddr, NattSocketKeepalive.NATT_PORT);
+                intervalSeconds, cb, srcAddr, srcPort, dstAddr, NattSocketKeepalive.NATT_PORT,
+                // Keep behavior of the deprecated method as it is. Set automaticOnOffKeepalives to
+                // false and set the underpinned network to null because there is no way and no
+                // plan to configure automaticOnOffKeepalives or underpinnedNetwork in this
+                // deprecated method.
+                false /* automaticOnOffKeepalives */, null /* underpinnedNetwork */);
     }
 
     @Override
     public void startNattKeepaliveWithFd(Network network, ParcelFileDescriptor pfd, int resourceId,
             int intervalSeconds, ISocketKeepaliveCallback cb, String srcAddr,
-            String dstAddr) {
+            String dstAddr, boolean automaticOnOffKeepalives, Network underpinnedNetwork) {
         try {
             final FileDescriptor fd = pfd.getFileDescriptor();
             mKeepaliveTracker.startNattKeepalive(
                     getNetworkAgentInfoForNetwork(network), fd, resourceId,
-                    intervalSeconds, cb,
-                    srcAddr, dstAddr, NattSocketKeepalive.NATT_PORT);
+                    intervalSeconds, cb, srcAddr, dstAddr, NattSocketKeepalive.NATT_PORT,
+                    automaticOnOffKeepalives, underpinnedNetwork);
         } finally {
             // FileDescriptors coming from AIDL calls must be manually closed to prevent leaks.
             // startNattKeepalive calls Os.dup(fd) before returning, so we can close immediately.
@@ -9671,9 +10199,20 @@
     }
 
     @Override
-    public void stopKeepalive(Network network, int slot) {
+    public void stopKeepalive(@NonNull final ISocketKeepaliveCallback cb) {
         mHandler.sendMessage(mHandler.obtainMessage(
-                NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE, slot, SocketKeepalive.SUCCESS, network));
+                NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE, 0, SocketKeepalive.SUCCESS,
+                Objects.requireNonNull(cb).asBinder()));
+    }
+
+    @Override
+    public int[] getSupportedKeepalives() {
+        enforceAnyPermissionOf(mContext, android.Manifest.permission.NETWORK_SETTINGS,
+                // Backwards compatibility with CTS 13
+                android.Manifest.permission.QUERY_ALL_PACKAGES);
+
+        return BinderUtils.withCleanCallingIdentity(() ->
+                KeepaliveResourceUtil.getSupportedKeepalives(mContext));
     }
 
     @Override
@@ -10388,6 +10927,18 @@
                         callback));
     }
 
+    private boolean hasUnderlyingTestNetworks(NetworkCapabilities nc) {
+        final List<Network> underlyingNetworks = nc.getUnderlyingNetworks();
+        if (underlyingNetworks == null) return false;
+
+        for (Network network : underlyingNetworks) {
+            if (getNetworkCapabilitiesInternal(network).hasTransport(TRANSPORT_TEST)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public void simulateDataStall(int detectionMethod, long timestampMillis,
             @NonNull Network network, @NonNull PersistableBundle extras) {
@@ -10398,14 +10949,18 @@
                 android.Manifest.permission.MANAGE_TEST_NETWORKS,
                 android.Manifest.permission.NETWORK_STACK);
         final NetworkCapabilities nc = getNetworkCapabilitiesInternal(network);
-        if (!nc.hasTransport(TRANSPORT_TEST)) {
-            throw new SecurityException("Data Stall simulation is only possible for test networks");
+        if (!nc.hasTransport(TRANSPORT_TEST) && !hasUnderlyingTestNetworks(nc)) {
+            throw new SecurityException(
+                    "Data Stall simulation is only possible for test networks or networks built on"
+                            + " top of test networks");
         }
 
         final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
-        if (nai == null || nai.creatorUid != mDeps.getCallingUid()) {
-            throw new SecurityException("Data Stall simulation is only possible for network "
-                + "creators");
+        if (nai == null
+                || (nai.creatorUid != mDeps.getCallingUid()
+                        && nai.creatorUid != Process.SYSTEM_UID)) {
+            throw new SecurityException(
+                    "Data Stall simulation is only possible for network " + "creators");
         }
 
         // Instead of passing the data stall directly to the ConnectivityDiagnostics handler, treat
@@ -10772,7 +11327,7 @@
         if (um.isManagedProfile(profile.getIdentifier())) {
             return true;
         }
-        if (SdkLevel.isAtLeastT() && dpm.getDeviceOwner() != null) return true;
+        if (mDeps.isAtLeastT() && dpm.getDeviceOwner() != null) return true;
         return false;
     }
 
@@ -10816,11 +11371,12 @@
                     + "or the device owner must be set. ");
         }
 
-        final List<ProfileNetworkPreferenceList.Preference> preferenceList = new ArrayList<>();
+        final List<ProfileNetworkPreferenceInfo> preferenceList = new ArrayList<>();
         boolean hasDefaultPreference = false;
         for (final ProfileNetworkPreference preference : preferences) {
             final NetworkCapabilities nc;
             boolean allowFallback = true;
+            boolean blockingNonEnterprise = false;
             switch (preference.getPreference()) {
                 case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT:
                     nc = null;
@@ -10830,6 +11386,9 @@
                                 "Invalid enterprise identifier in setProfileNetworkPreferences");
                     }
                     break;
+                case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING:
+                    blockingNonEnterprise = true;
+                    // continue to process the enterprise preference.
                 case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK:
                     allowFallback = false;
                     // continue to process the enterprise preference.
@@ -10863,8 +11422,8 @@
                     throw new IllegalArgumentException(
                             "Invalid preference in setProfileNetworkPreferences");
             }
-            preferenceList.add(new ProfileNetworkPreferenceList.Preference(
-                    profile, nc, allowFallback));
+            preferenceList.add(new ProfileNetworkPreferenceInfo(
+                    profile, nc, allowFallback, blockingNonEnterprise));
             if (hasDefaultPreference && preferenceList.size() > 1) {
                 throw new IllegalArgumentException(
                         "Default profile preference should not be set along with other preference");
@@ -10913,9 +11472,9 @@
     }
 
     private ArraySet<NetworkRequestInfo> createNrisFromProfileNetworkPreferences(
-            @NonNull final ProfileNetworkPreferenceList prefs) {
+            @NonNull final NetworkPreferenceList<UserHandle, ProfileNetworkPreferenceInfo> prefs) {
         final ArraySet<NetworkRequestInfo> result = new ArraySet<>();
-        for (final ProfileNetworkPreferenceList.Preference pref : prefs.preferences) {
+        for (final ProfileNetworkPreferenceInfo pref : prefs) {
             // The NRI for a user should contain the request for capabilities.
             // If fallback to default network is needed then NRI should include
             // the request for the default network. Create an image of it to
@@ -10945,12 +11504,12 @@
      *
      */
     private boolean isRangeAlreadyInPreferenceList(
-            @NonNull List<ProfileNetworkPreferenceList.Preference> preferenceList,
+            @NonNull List<ProfileNetworkPreferenceInfo> preferenceList,
             @NonNull Set<UidRange> uidRangeSet) {
         if (uidRangeSet.size() == 0 || preferenceList.size() == 0) {
             return false;
         }
-        for (ProfileNetworkPreferenceList.Preference pref : preferenceList) {
+        for (ProfileNetworkPreferenceInfo pref : preferenceList) {
             if (UidRangeUtils.doesRangeSetOverlap(
                     UidRange.fromIntRanges(pref.capabilities.getUids()), uidRangeSet)) {
                 return true;
@@ -10960,7 +11519,7 @@
     }
 
     private void handleSetProfileNetworkPreference(
-            @NonNull final List<ProfileNetworkPreferenceList.Preference> preferenceList,
+            @NonNull final List<ProfileNetworkPreferenceInfo> preferenceList,
             @Nullable final IOnCompleteListener listener) {
         /*
          * handleSetProfileNetworkPreference is always called for single user.
@@ -10969,15 +11528,15 @@
          * Clear all the existing preferences for the user before applying new preferences.
          *
          */
-        mProfileNetworkPreferences = mProfileNetworkPreferences.withoutUser(
-                preferenceList.get(0).user);
-        for (final ProfileNetworkPreferenceList.Preference preference : preferenceList) {
+        mProfileNetworkPreferences = mProfileNetworkPreferences.minus(preferenceList.get(0).user);
+        for (final ProfileNetworkPreferenceInfo preference : preferenceList) {
             mProfileNetworkPreferences = mProfileNetworkPreferences.plus(preference);
         }
 
         removeDefaultNetworkRequestsForPreference(PREFERENCE_ORDER_PROFILE);
         addPerAppDefaultNetworkRequests(
                 createNrisFromProfileNetworkPreferences(mProfileNetworkPreferences));
+        updateProfileAllowedNetworks();
 
         // Finally, rematch.
         rematchAllNetworksAndRequests();
@@ -11050,7 +11609,7 @@
 
     private boolean canNetworkBeRateLimited(@NonNull final NetworkAgentInfo networkAgent) {
         // Rate-limiting cannot run correctly before T because the BPF program is not loaded.
-        if (!SdkLevel.isAtLeastT()) return false;
+        if (!mDeps.isAtLeastT()) return false;
 
         final NetworkCapabilities agentCaps = networkAgent.networkCapabilities;
         // Only test networks (they cannot hold NET_CAPABILITY_INTERNET) and networks that provide
@@ -11107,6 +11666,60 @@
     }
 
     /**
+     * Sets the specified UIDs to get/receive the VPN as the only default network.
+     *
+     * Calling this will overwrite the existing network preference for this session, and the
+     * specified UIDs won't get any default network when no VPN is connected.
+     *
+     * @param session The VPN session which manages the passed UIDs.
+     * @param ranges The uid ranges which will treat VPN as the only preferred network. Clear the
+     *               setting for this session if the array is empty. Null is not allowed, the
+     *               method will use {@link Objects#requireNonNull(Object)} to check this variable.
+     * @hide
+     */
+    @Override
+    public void setVpnNetworkPreference(String session, UidRange[] ranges) {
+        Objects.requireNonNull(ranges);
+        enforceNetworkStackOrSettingsPermission();
+        final UidRange[] sortedRanges = UidRangeUtils.sortRangesByStartUid(ranges);
+        if (UidRangeUtils.sortedRangesContainOverlap(sortedRanges)) {
+            throw new IllegalArgumentException(
+                    "setVpnNetworkPreference: Passed UID ranges overlap");
+        }
+
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_VPN_NETWORK_PREFERENCE,
+                new VpnNetworkPreferenceInfo(session,
+                        new ArraySet<UidRange>(Arrays.asList(ranges)))));
+    }
+
+    private void handleSetVpnNetworkPreference(VpnNetworkPreferenceInfo preferenceInfo) {
+        Log.d(TAG, "handleSetVpnNetworkPreference: preferenceInfo = " + preferenceInfo);
+
+        mVpnNetworkPreferences = mVpnNetworkPreferences.minus(preferenceInfo.getKey());
+        mVpnNetworkPreferences = mVpnNetworkPreferences.plus(preferenceInfo);
+
+        removeDefaultNetworkRequestsForPreference(PREFERENCE_ORDER_VPN);
+        addPerAppDefaultNetworkRequests(createNrisForVpnNetworkPreference(mVpnNetworkPreferences));
+        // Finally, rematch.
+        rematchAllNetworksAndRequests();
+    }
+
+    private ArraySet<NetworkRequestInfo> createNrisForVpnNetworkPreference(
+            @NonNull NetworkPreferenceList<String, VpnNetworkPreferenceInfo> preferenceList) {
+        final ArraySet<NetworkRequestInfo> nris = new ArraySet<>();
+        for (VpnNetworkPreferenceInfo preferenceInfo : preferenceList) {
+            final List<NetworkRequest> requests = new ArrayList<>();
+            // Request VPN only, so other networks won't be the fallback options when VPN is not
+            // connected temporarily.
+            requests.add(createVpnRequest());
+            final Set<UidRange> uidRanges = new ArraySet(preferenceInfo.getUidRangesNoCopy());
+            setNetworkRequestUids(requests, uidRanges);
+            nris.add(new NetworkRequestInfo(Process.myUid(), requests, PREFERENCE_ORDER_VPN));
+        }
+        return nris;
+    }
+
+    /**
      * Check the validity of an OEM network preference to be used for testing purposes.
      * @param preference the preference to validate
      * @return true if this is a valid OEM network preference test request.
@@ -11447,6 +12060,12 @@
         }
     }
 
+    @Override
+    public int getUidFirewallRule(final int chain, final int uid) {
+        enforceNetworkStackOrSettingsPermission();
+        return mBpfNetMaps.getUidRule(chain, uid);
+    }
+
     private int getFirewallRuleType(int chain, int rule) {
         final int defaultRule;
         switch (chain) {
@@ -11470,6 +12089,23 @@
         return rule;
     }
 
+    private void closeSocketsForFirewallChainLocked(final int chain)
+            throws ErrnoException, SocketException, InterruptedIOException {
+        if (mBpfNetMaps.isFirewallAllowList(chain)) {
+            // Allowlist means the firewall denies all by default, uids must be explicitly allowed
+            // So, close all non-system socket owned by uids that are not explicitly allowed
+            Set<Range<Integer>> ranges = new ArraySet<>();
+            ranges.add(new Range<>(Process.FIRST_APPLICATION_UID, Integer.MAX_VALUE));
+            final Set<Integer> exemptUids = mBpfNetMaps.getUidsWithAllowRuleOnAllowListChain(chain);
+            mDeps.destroyLiveTcpSockets(ranges, exemptUids);
+        } else {
+            // Denylist means the firewall allows all by default, uids must be explicitly denied
+            // So, close socket owned by uids that are explicitly denied
+            final Set<Integer> ownerUids = mBpfNetMaps.getUidsWithDenyRuleOnDenyListChain(chain);
+            mDeps.destroyLiveTcpSocketsByOwnerUids(ownerUids);
+        }
+    }
+
     @Override
     public void setFirewallChainEnabled(final int chain, final boolean enable) {
         enforceNetworkStackOrSettingsPermission();
@@ -11479,6 +12115,14 @@
         } catch (ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
+
+        if (mDeps.isAtLeastU() && enable) {
+            try {
+                closeSocketsForFirewallChainLocked(chain);
+            } catch (ErrnoException | SocketException | InterruptedIOException e) {
+                Log.e(TAG, "Failed to close sockets after enabling chain (" + chain + "): " + e);
+            }
+        }
     }
 
     @Override
@@ -11494,4 +12138,10 @@
 
         mBpfNetMaps.replaceUidChain(chain, uids);
     }
+
+    @Override
+    public IBinder getCompanionDeviceManagerProxyService() {
+        enforceNetworkStackPermission(mContext);
+        return mCdmps;
+    }
 }
diff --git a/service/src/com/android/server/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index 5549fbe..843b7b3 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -310,9 +310,11 @@
                     NetworkStackConstants.IPV6_ADDR_ANY, 0), null, iface));
         }
 
+        // For testing purpose, fill legacy type for NetworkStatsService since it does not
+        // support transport types.
         final TestNetworkAgent agent = new TestNetworkAgent(context, looper, nc, lp,
-                new NetworkAgentConfig.Builder().build(), callingUid, binder,
-                mNetworkProvider);
+                new NetworkAgentConfig.Builder().setLegacyType(ConnectivityManager.TYPE_TEST)
+                        .build(), callingUid, binder, mNetworkProvider);
         agent.register();
         agent.markConnected();
         return agent;
diff --git a/service/src/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilities.java b/service/src/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilities.java
new file mode 100644
index 0000000..76e966f
--- /dev/null
+++ b/service/src/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilities.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+
+import android.annotation.NonNull;
+import android.net.NetworkCapabilities;
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayDeque;
+
+
+/**
+ * The class for parsing and checking the self-declared application network capabilities.
+ *
+ * ApplicationSelfCertifiedNetworkCapabilities is an immutable class that
+ * can parse the self-declared application network capabilities in the application resources. The
+ * class also provides a helper method to check whether the requested network capabilities
+ * already self-declared.
+ */
+public final class ApplicationSelfCertifiedNetworkCapabilities {
+
+    public static final String PRIORITIZE_LATENCY = "NET_CAPABILITY_PRIORITIZE_LATENCY";
+    public static final String PRIORITIZE_BANDWIDTH = "NET_CAPABILITY_PRIORITIZE_BANDWIDTH";
+
+    private static final String TAG =
+            ApplicationSelfCertifiedNetworkCapabilities.class.getSimpleName();
+    private static final String NETWORK_CAPABILITIES_DECLARATION_TAG =
+            "network-capabilities-declaration";
+    private static final String USES_NETWORK_CAPABILITY_TAG = "uses-network-capability";
+    private static final String NAME_TAG = "name";
+
+    private long mRequestedNetworkCapabilities = 0;
+
+    /**
+     * Creates {@link ApplicationSelfCertifiedNetworkCapabilities} from a xml parser.
+     *
+     * <p> Here is an example of the xml syntax:
+     *
+     * <pre>
+     * {@code
+     *  <network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+     *     <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+     *     <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+     * </network-capabilities-declaration>
+     * }
+     * </pre>
+     * <p>
+     *
+     * @param xmlParser The underlying {@link XmlPullParser} that will read the xml.
+     * @return An ApplicationSelfCertifiedNetworkCapabilities object.
+     * @throws InvalidTagException    if the capabilities in xml config contains invalid tag.
+     * @throws XmlPullParserException if xml parsing failed.
+     * @throws IOException            if unable to read the xml file properly.
+     */
+    @NonNull
+    public static ApplicationSelfCertifiedNetworkCapabilities createFromXml(
+            @NonNull final XmlPullParser xmlParser)
+            throws InvalidTagException, XmlPullParserException, IOException {
+        return new ApplicationSelfCertifiedNetworkCapabilities(parseXml(xmlParser));
+    }
+
+    private static long parseXml(@NonNull final XmlPullParser xmlParser)
+            throws InvalidTagException, XmlPullParserException, IOException {
+        long requestedNetworkCapabilities = 0;
+        final ArrayDeque<String> openTags = new ArrayDeque<>();
+
+        while (checkedNextTag(xmlParser, openTags) != XmlPullParser.START_TAG) {
+            continue;
+        }
+
+        // Validates the tag is "network-capabilities-declaration"
+        if (!xmlParser.getName().equals(NETWORK_CAPABILITIES_DECLARATION_TAG)) {
+            throw new InvalidTagException("Invalid tag: " + xmlParser.getName());
+        }
+
+        checkedNextTag(xmlParser, openTags);
+        int eventType = xmlParser.getEventType();
+        while (eventType != XmlPullParser.END_DOCUMENT) {
+            switch (eventType) {
+                case XmlPullParser.START_TAG:
+                    // USES_NETWORK_CAPABILITY_TAG should directly be declared under
+                    // NETWORK_CAPABILITIES_DECLARATION_TAG.
+                    if (xmlParser.getName().equals(USES_NETWORK_CAPABILITY_TAG)
+                            && openTags.size() == 1) {
+                        int capability = parseDeclarationTag(xmlParser);
+                        if (capability >= 0) {
+                            requestedNetworkCapabilities |= 1L << capability;
+                        }
+                    } else {
+                        Log.w(TAG, "Unknown tag: " + xmlParser.getName() + " ,tags stack size: "
+                                + openTags.size());
+                    }
+                    break;
+                default:
+                    break;
+            }
+            eventType = checkedNextTag(xmlParser, openTags);
+        }
+        // Checks all the tags are parsed.
+        if (!openTags.isEmpty()) {
+            throw new InvalidTagException("Unbalanced tag: " + openTags.peek());
+        }
+        return requestedNetworkCapabilities;
+    }
+
+    private static int parseDeclarationTag(@NonNull final XmlPullParser xmlParser) {
+        String name = null;
+        for (int i = 0; i < xmlParser.getAttributeCount(); i++) {
+            final String attrName = xmlParser.getAttributeName(i);
+            if (attrName.equals(NAME_TAG)) {
+                name = xmlParser.getAttributeValue(i);
+            } else {
+                Log.w(TAG, "Unknown attribute name: " + attrName);
+            }
+        }
+        if (name != null) {
+            switch (name) {
+                case PRIORITIZE_BANDWIDTH:
+                    return NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
+                case PRIORITIZE_LATENCY:
+                    return NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY;
+                default:
+                    Log.w(TAG, "Unknown capability declaration name: " + name);
+            }
+        } else {
+            Log.w(TAG, "uses-network-capability name must be specified");
+        }
+        // Invalid capability
+        return -1;
+    }
+
+    private static int checkedNextTag(@NonNull final XmlPullParser xmlParser,
+            @NonNull final ArrayDeque<String> openTags)
+            throws XmlPullParserException, IOException, InvalidTagException {
+        if (xmlParser.getEventType() == XmlPullParser.START_TAG) {
+            openTags.addFirst(xmlParser.getName());
+        } else if (xmlParser.getEventType() == XmlPullParser.END_TAG) {
+            if (!openTags.isEmpty() && openTags.peekFirst().equals(xmlParser.getName())) {
+                openTags.removeFirst();
+            } else {
+                throw new InvalidTagException("Unbalanced tag: " + xmlParser.getName());
+            }
+        }
+        return xmlParser.next();
+    }
+
+    private ApplicationSelfCertifiedNetworkCapabilities(long requestedNetworkCapabilities) {
+        mRequestedNetworkCapabilities = requestedNetworkCapabilities;
+    }
+
+    /**
+     * Enforces self-certified capabilities are declared.
+     *
+     * @param networkCapabilities the input NetworkCapabilities to check against.
+     * @throws SecurityException if the capabilities are not properly self-declared.
+     */
+    public void enforceSelfCertifiedNetworkCapabilitiesDeclared(
+            @NonNull final NetworkCapabilities networkCapabilities) {
+        if (networkCapabilities.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                && !hasPrioritizeBandwidth()) {
+            throw new SecurityException(
+                    "Missing " + ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_BANDWIDTH
+                            + " declaration");
+        }
+        if (networkCapabilities.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                && !hasPrioritizeLatency()) {
+            throw new SecurityException(
+                    "Missing " + ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_LATENCY
+                            + " declaration");
+        }
+    }
+
+    /**
+     * Checks if NET_CAPABILITY_PRIORITIZE_LATENCY is declared.
+     */
+    private boolean hasPrioritizeLatency() {
+        return (mRequestedNetworkCapabilities & (1L
+                << NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)) != 0;
+    }
+
+    /**
+     * Checks if NET_CAPABILITY_PRIORITIZE_BANDWIDTH is declared.
+     */
+    private boolean hasPrioritizeBandwidth() {
+        return (mRequestedNetworkCapabilities & (1L
+                << NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)) != 0;
+    }
+}
diff --git a/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
new file mode 100644
index 0000000..f8285ed
--- /dev/null
+++ b/service/src/com/android/server/connectivity/AutomaticOnOffKeepaliveTracker.java
@@ -0,0 +1,861 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
+import static android.net.SocketKeepalive.MIN_INTERVAL_SEC;
+import static android.net.SocketKeepalive.SUCCESS_PAUSED;
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_SNDTIMEO;
+
+import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE;
+import static com.android.net.module.util.netlink.NetlinkConstants.SOCKDIAG_MSG_HEADER_SIZE;
+import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
+import static com.android.net.module.util.netlink.NetlinkUtils.IO_TIMEOUT_MS;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.net.INetd;
+import android.net.ISocketKeepaliveCallback;
+import android.net.MarkMaskParcel;
+import android.net.Network;
+import android.net.SocketKeepalive.InvalidSocketException;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructTimeval;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.BinderUtils;
+import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.DeviceConfigUtils;
+import com.android.net.module.util.HexDump;
+import com.android.net.module.util.SocketUtils;
+import com.android.net.module.util.netlink.InetDiagMessage;
+import com.android.net.module.util.netlink.NetlinkMessage;
+import com.android.net.module.util.netlink.NetlinkUtils;
+import com.android.net.module.util.netlink.StructNlAttr;
+
+import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.SocketException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Manages automatic on/off socket keepalive requests.
+ *
+ * Provides methods to stop and start automatic keepalive requests, and keeps track of keepalives
+ * across all networks. This class is tightly coupled to ConnectivityService. It is not
+ * thread-safe and its handle* methods must be called only from the ConnectivityService handler
+ * thread.
+ */
+public class AutomaticOnOffKeepaliveTracker {
+    private static final String TAG = "AutomaticOnOffKeepaliveTracker";
+    private static final int[] ADDRESS_FAMILIES = new int[] {AF_INET6, AF_INET};
+    private static final long LOW_TCP_POLLING_INTERVAL_MS = 1_000L;
+    private static final int ADJUST_TCP_POLLING_DELAY_MS = 2000;
+    private static final String AUTOMATIC_ON_OFF_KEEPALIVE_VERSION =
+            "automatic_on_off_keepalive_version";
+
+    // ConnectivityService parses message constants from itself and AutomaticOnOffKeepaliveTracker
+    // with MessageUtils for debugging purposes, and crashes if some messages have the same values.
+    private static final int BASE = 2000;
+    /**
+     * Sent by AutomaticOnOffKeepaliveTracker periodically (when relevant) to trigger monitor
+     * automatic keepalive request.
+     *
+     * NATT keepalives have an automatic mode where the system only sends keepalive packets when
+     * TCP sockets are open over a VPN. The system will check periodically for presence of
+     * such open sockets, and this message is what triggers the re-evaluation.
+     *
+     * obj = A Binder object associated with the keepalive.
+     */
+    public static final int CMD_MONITOR_AUTOMATIC_KEEPALIVE = BASE + 1;
+
+    /**
+     * Sent by AutomaticOnOffKeepaliveTracker to ConnectivityService to start a keepalive.
+     *
+     * obj = AutomaticKeepaliveInfo object
+     */
+    public static final int CMD_REQUEST_START_KEEPALIVE = BASE + 2;
+
+    /**
+     * States for {@code #AutomaticOnOffKeepalive}.
+     *
+     * If automatic mode is off for this keepalive, the state is STATE_ALWAYS_ON and it stays
+     * so for the entire lifetime of this object.
+     *
+     * If enabled, a new AutomaticOnOffKeepalive starts with STATE_ENABLED. The system will monitor
+     * the TCP sockets on VPN networks running on top of the specified network, and turn off
+     * keepalive if there is no TCP socket any of the VPN networks. Conversely, it will turn
+     * keepalive back on if any TCP socket is open on any of the VPN networks.
+     *
+     * When there is no TCP socket on any of the VPN networks, the state becomes STATE_SUSPENDED.
+     * The {@link KeepaliveTracker.KeepaliveInfo} object is kept to remember the parameters so it
+     * is possible to resume keepalive later with the same parameters.
+     *
+     * When the system detects some TCP socket is open on one of the VPNs while in STATE_SUSPENDED,
+     * this AutomaticOnOffKeepalive goes to STATE_ENABLED again.
+     *
+     * When finishing keepalive, this object is deleted.
+     */
+    private static final int STATE_ENABLED = 0;
+    private static final int STATE_SUSPENDED = 1;
+    private static final int STATE_ALWAYS_ON = 2;
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "STATE_" }, value = {
+            STATE_ENABLED,
+            STATE_SUSPENDED,
+            STATE_ALWAYS_ON
+    })
+    private @interface AutomaticOnOffState {}
+
+    @NonNull
+    private final Handler mConnectivityServiceHandler;
+    @NonNull
+    private final KeepaliveTracker mKeepaliveTracker;
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final AlarmManager mAlarmManager;
+
+    /**
+     * The {@code inetDiagReqV2} messages for different IP family.
+     *
+     *   Key: Ip family type.
+     * Value: Bytes array represent the {@code inetDiagReqV2}.
+     *
+     * This should only be accessed in the connectivity service handler thread.
+     */
+    private final SparseArray<byte[]> mSockDiagMsg = new SparseArray<>();
+    private final Dependencies mDependencies;
+    private final INetd mNetd;
+    /**
+     * Keeps track of automatic on/off keepalive requests.
+     * This should be only updated in ConnectivityService handler thread.
+     */
+    private final ArrayList<AutomaticOnOffKeepalive> mAutomaticOnOffKeepalives = new ArrayList<>();
+    // TODO: Remove this when TCP polling design is replaced with callback.
+    private long mTestLowTcpPollingTimerUntilMs = 0;
+
+    private static final int MAX_EVENTS_LOGS = 40;
+    private final LocalLog mEventLog = new LocalLog(MAX_EVENTS_LOGS);
+
+    private final KeepaliveStatsTracker mKeepaliveStatsTracker = new KeepaliveStatsTracker();
+    /**
+     * Information about a managed keepalive.
+     *
+     * The keepalive in mKi is managed by this object. This object can be in one of three states
+     * (in mAutomatiOnOffState) :
+     * • STATE_ALWAYS_ON : this keepalive is always on
+     * • STATE_ENABLED : this keepalive is currently on, and monitored for possibly being turned
+     *                   off if no TCP socket is open on the VPN.
+     * • STATE_SUSPENDED : this keepalive is currently off, and monitored for possibly being
+     *                     resumed if a TCP socket is open on the VPN.
+     * See the documentation for the states for more detail.
+     */
+    public class AutomaticOnOffKeepalive implements IBinder.DeathRecipient {
+        @NonNull
+        private final KeepaliveTracker.KeepaliveInfo mKi;
+        @NonNull
+        private final ISocketKeepaliveCallback mCallback;
+        @Nullable
+        private final FileDescriptor mFd;
+        @Nullable
+        private final AlarmManager.OnAlarmListener mAlarmListener;
+        @AutomaticOnOffState
+        private int mAutomaticOnOffState;
+        @Nullable
+        private final Network mUnderpinnedNetwork;
+
+        AutomaticOnOffKeepalive(@NonNull final KeepaliveTracker.KeepaliveInfo ki,
+                final boolean autoOnOff, @Nullable Network underpinnedNetwork)
+                throws InvalidSocketException {
+            this.mKi = Objects.requireNonNull(ki);
+            mCallback = ki.mCallback;
+            mUnderpinnedNetwork = underpinnedNetwork;
+            // Reading DeviceConfig will check if the calling uid and calling package name are the
+            // same. Clear calling identity to align the calling uid and package
+            final boolean enabled = BinderUtils.withCleanCallingIdentity(
+                    () -> mDependencies.isFeatureEnabled(AUTOMATIC_ON_OFF_KEEPALIVE_VERSION,
+                            true /* defaultEnabled */));
+            if (autoOnOff && enabled) {
+                mAutomaticOnOffState = STATE_ENABLED;
+                if (null == ki.mFd) {
+                    throw new IllegalArgumentException("fd can't be null with automatic "
+                            + "on/off keepalives");
+                }
+                try {
+                    mFd = Os.dup(ki.mFd);
+                } catch (ErrnoException e) {
+                    Log.e(TAG, "Cannot dup fd: ", e);
+                    throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
+                }
+                mAlarmListener = () -> mConnectivityServiceHandler.obtainMessage(
+                        CMD_MONITOR_AUTOMATIC_KEEPALIVE, mCallback.asBinder())
+                        .sendToTarget();
+            } else {
+                mAutomaticOnOffState = STATE_ALWAYS_ON;
+                // A null fd is acceptable in KeepaliveInfo for backward compatibility of
+                // PacketKeepalive API, but it must never happen with automatic keepalives.
+                // TODO : remove mFd from KeepaliveInfo or from this class.
+                mFd = ki.mFd;
+                mAlarmListener = null;
+            }
+        }
+
+        @VisibleForTesting
+        public ISocketKeepaliveCallback getCallback() {
+            return mCallback;
+        }
+
+        public Network getNetwork() {
+            return mKi.getNai().network;
+        }
+
+        @Nullable
+        public Network getUnderpinnedNetwork() {
+            return mUnderpinnedNetwork;
+        }
+
+        public boolean match(Network network, int slot) {
+            return mKi.getNai().network().equals(network) && mKi.getSlot() == slot;
+        }
+
+        @Override
+        public void binderDied() {
+            mEventLog.log("Binder died : " + mCallback);
+            mConnectivityServiceHandler.post(() -> cleanupAutoOnOffKeepalive(this));
+        }
+
+        /** Close this automatic on/off keepalive */
+        public void close() {
+            // Close the duplicated fd that maintains the lifecycle of socket. If this fd was
+            // not duplicated this is a no-op.
+            FileUtils.closeQuietly(mFd);
+        }
+
+        private String getAutomaticOnOffStateName(int state) {
+            switch (state) {
+                case STATE_ENABLED:
+                    return "STATE_ENABLED";
+                case STATE_SUSPENDED:
+                    return "STATE_SUSPENDED";
+                case STATE_ALWAYS_ON:
+                    return "STATE_ALWAYS_ON";
+                default:
+                    Log.e(TAG, "Get unexpected state:" + state);
+                    return Integer.toString(state);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "AutomaticOnOffKeepalive [ "
+                    + mKi
+                    + ", state=" + getAutomaticOnOffStateName(mAutomaticOnOffState)
+                    + " ]";
+        }
+    }
+
+    public AutomaticOnOffKeepaliveTracker(@NonNull Context context, @NonNull Handler handler) {
+        this(context, handler, new Dependencies(context));
+    }
+
+    @VisibleForTesting
+    public AutomaticOnOffKeepaliveTracker(@NonNull Context context, @NonNull Handler handler,
+            @NonNull Dependencies dependencies) {
+        mContext = Objects.requireNonNull(context);
+        mDependencies = Objects.requireNonNull(dependencies);
+        mConnectivityServiceHandler = Objects.requireNonNull(handler);
+        mNetd = mDependencies.getNetd();
+        mKeepaliveTracker = mDependencies.newKeepaliveTracker(
+                mContext, mConnectivityServiceHandler);
+
+        mAlarmManager = mDependencies.getAlarmManager(context);
+    }
+
+    private void startTcpPollingAlarm(@NonNull AutomaticOnOffKeepalive ki) {
+        if (ki.mAlarmListener == null) return;
+
+        final long triggerAtMillis =
+                mDependencies.getElapsedRealtime() + getTcpPollingIntervalMs(ki);
+        // Setup a non-wake up alarm.
+        mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME, triggerAtMillis, null /* tag */,
+                ki.mAlarmListener, mConnectivityServiceHandler);
+    }
+
+    /**
+     * Determine if any state transition is needed for the specific automatic keepalive.
+     */
+    public void handleMonitorAutomaticKeepalive(@NonNull final AutomaticOnOffKeepalive ki,
+            final int vpnNetId) {
+        // Might happen if the automatic keepalive was removed by the app just as the alarm fires.
+        if (!mAutomaticOnOffKeepalives.contains(ki)) return;
+        if (STATE_ALWAYS_ON == ki.mAutomaticOnOffState) {
+            throw new IllegalStateException("Should not monitor non-auto keepalive");
+        }
+
+        handleMonitorTcpConnections(ki, vpnNetId);
+    }
+
+    /**
+     * Determine if disable or re-enable keepalive is needed or not based on TCP sockets status.
+     */
+    private void handleMonitorTcpConnections(@NonNull AutomaticOnOffKeepalive ki, int vpnNetId) {
+        // Might happen if the automatic keepalive was removed by the app just as the alarm fires.
+        if (!mAutomaticOnOffKeepalives.contains(ki)) return;
+        if (STATE_ALWAYS_ON == ki.mAutomaticOnOffState) {
+            throw new IllegalStateException("Should not monitor non-auto keepalive");
+        }
+        if (!isAnyTcpSocketConnected(vpnNetId)) {
+            // No TCP socket exists. Stop keepalive if ENABLED, and remain SUSPENDED if currently
+            // SUSPENDED.
+            if (ki.mAutomaticOnOffState == STATE_ENABLED) {
+                ki.mAutomaticOnOffState = STATE_SUSPENDED;
+                handlePauseKeepalive(ki.mKi);
+            }
+        } else {
+            handleMaybeResumeKeepalive(ki);
+        }
+        // TODO: listen to socket status instead of periodically check.
+        startTcpPollingAlarm(ki);
+    }
+
+    /**
+     * Resume an auto on/off keepalive, unless it's already resumed
+     * @param autoKi the keepalive to resume
+     */
+    public void handleMaybeResumeKeepalive(@NonNull AutomaticOnOffKeepalive autoKi) {
+        mEventLog.log("Resume keepalive " + autoKi.mCallback + " on " + autoKi.getNetwork());
+        // Might happen if the automatic keepalive was removed by the app just as the alarm fires.
+        if (!mAutomaticOnOffKeepalives.contains(autoKi)) return;
+        if (STATE_ALWAYS_ON == autoKi.mAutomaticOnOffState) {
+            throw new IllegalStateException("Should not resume non-auto keepalive");
+        }
+        if (autoKi.mAutomaticOnOffState == STATE_ENABLED) return;
+        KeepaliveTracker.KeepaliveInfo newKi;
+        try {
+            // Get fd from AutomaticOnOffKeepalive since the fd in the original
+            // KeepaliveInfo should be closed.
+            newKi = autoKi.mKi.withFd(autoKi.mFd);
+        } catch (InvalidSocketException | IllegalArgumentException | SecurityException e) {
+            Log.e(TAG, "Fail to construct keepalive", e);
+            mKeepaliveTracker.notifyErrorCallback(autoKi.mCallback, ERROR_INVALID_SOCKET);
+            return;
+        }
+        autoKi.mAutomaticOnOffState = STATE_ENABLED;
+        handleResumeKeepalive(newKi);
+    }
+
+    /**
+     * Find the AutomaticOnOffKeepalive associated with a given callback.
+     * @return the keepalive associated with this callback, or null if none
+     */
+    @Nullable
+    public AutomaticOnOffKeepalive getKeepaliveForBinder(@NonNull final IBinder token) {
+        ensureRunningOnHandlerThread();
+
+        return CollectionUtils.findFirst(mAutomaticOnOffKeepalives,
+                it -> it.mCallback.asBinder().equals(token));
+    }
+
+    /**
+     * Handle keepalive events from lower layer.
+     *
+     * Forward to KeepaliveTracker.
+     */
+    public void handleEventSocketKeepalive(@NonNull NetworkAgentInfo nai, int slot, int reason) {
+        mKeepaliveTracker.handleEventSocketKeepalive(nai, slot, reason);
+    }
+
+    /**
+     * Handle stop all keepalives on the specific network.
+     */
+    public void handleStopAllKeepalives(NetworkAgentInfo nai, int reason) {
+        mEventLog.log("Stop all keepalives on " + nai.network + " because " + reason);
+        mKeepaliveTracker.handleStopAllKeepalives(nai, reason);
+        final List<AutomaticOnOffKeepalive> matches =
+                CollectionUtils.filter(mAutomaticOnOffKeepalives, it -> it.mKi.getNai() == nai);
+        for (final AutomaticOnOffKeepalive ki : matches) {
+            cleanupAutoOnOffKeepalive(ki);
+        }
+    }
+
+    /**
+     * Handle start keepalive contained within a message.
+     *
+     * The message is expected to contain a KeepaliveTracker.KeepaliveInfo.
+     */
+    public void handleStartKeepalive(Message message) {
+        final AutomaticOnOffKeepalive autoKi = (AutomaticOnOffKeepalive) message.obj;
+        mEventLog.log("Start keepalive " + autoKi.mCallback + " on " + autoKi.getNetwork());
+        mKeepaliveStatsTracker.onStartKeepalive();
+        mKeepaliveTracker.handleStartKeepalive(autoKi.mKi);
+
+        // Add automatic on/off request into list to track its life cycle.
+        try {
+            autoKi.mKi.mCallback.asBinder().linkToDeath(autoKi, 0);
+        } catch (RemoteException e) {
+            // The underlying keepalive performs its own cleanup
+            autoKi.binderDied();
+            return;
+        }
+        mAutomaticOnOffKeepalives.add(autoKi);
+        if (STATE_ALWAYS_ON != autoKi.mAutomaticOnOffState) {
+            startTcpPollingAlarm(autoKi);
+        }
+    }
+
+    private void handleResumeKeepalive(@NonNull final KeepaliveTracker.KeepaliveInfo ki) {
+        mKeepaliveStatsTracker.onResumeKeepalive();
+        mKeepaliveTracker.handleStartKeepalive(ki);
+        mEventLog.log("Resumed successfully keepalive " + ki.mCallback + " on " + ki.mNai);
+    }
+
+    private void handlePauseKeepalive(@NonNull final KeepaliveTracker.KeepaliveInfo ki) {
+        mEventLog.log("Suspend keepalive " + ki.mCallback + " on " + ki.mNai);
+        mKeepaliveStatsTracker.onPauseKeepalive();
+        // TODO : mKT.handleStopKeepalive should take a KeepaliveInfo instead
+        mKeepaliveTracker.handleStopKeepalive(ki.getNai(), ki.getSlot(), SUCCESS_PAUSED);
+    }
+
+    /**
+     * Handle stop keepalives on the specific network with given slot.
+     */
+    public void handleStopKeepalive(@NonNull final AutomaticOnOffKeepalive autoKi, int reason) {
+        mEventLog.log("Stop keepalive " + autoKi.mCallback + " because " + reason);
+        // Stop the keepalive unless it was suspended. This includes the case where it's managed
+        // but enabled, and the case where it's always on.
+        if (autoKi.mAutomaticOnOffState != STATE_SUSPENDED) {
+            final KeepaliveTracker.KeepaliveInfo ki = autoKi.mKi;
+            mKeepaliveTracker.handleStopKeepalive(ki.getNai(), ki.getSlot(), reason);
+        } else {
+            mKeepaliveTracker.finalizePausedKeepalive(autoKi.mKi);
+        }
+
+        cleanupAutoOnOffKeepalive(autoKi);
+    }
+
+    private void cleanupAutoOnOffKeepalive(@NonNull final AutomaticOnOffKeepalive autoKi) {
+        ensureRunningOnHandlerThread();
+        mKeepaliveStatsTracker.onStopKeepalive(autoKi.mAutomaticOnOffState != STATE_SUSPENDED);
+        autoKi.close();
+        if (null != autoKi.mAlarmListener) mAlarmManager.cancel(autoKi.mAlarmListener);
+
+        // If the KI is not in the array, it's because it was already removed, or it was never
+        // added ; the only ways this can happen is if the keepalive is stopped by the app and the
+        // app dies immediately, or if the app died before the link to death could be registered.
+        if (!mAutomaticOnOffKeepalives.remove(autoKi)) return;
+
+        autoKi.mKi.mCallback.asBinder().unlinkToDeath(autoKi, 0);
+    }
+
+    /**
+     * Called when requesting that keepalives be started on a IPsec NAT-T socket. See
+     * {@link android.net.SocketKeepalive}.
+     *
+     * Forward to KeepaliveTracker.
+     **/
+    public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+            @Nullable FileDescriptor fd,
+            int intervalSeconds,
+            @NonNull ISocketKeepaliveCallback cb,
+            @NonNull String srcAddrString,
+            int srcPort,
+            @NonNull String dstAddrString,
+            int dstPort, boolean automaticOnOffKeepalives, @Nullable Network underpinnedNetwork) {
+        final KeepaliveTracker.KeepaliveInfo ki = mKeepaliveTracker.makeNattKeepaliveInfo(nai, fd,
+                intervalSeconds, cb, srcAddrString, srcPort, dstAddrString, dstPort);
+        if (null == ki) return;
+        try {
+            final AutomaticOnOffKeepalive autoKi = new AutomaticOnOffKeepalive(ki,
+                    automaticOnOffKeepalives, underpinnedNetwork);
+            mEventLog.log("Start natt keepalive " + cb + " on " + nai.network
+                    + " " + srcAddrString + ":" + srcPort
+                    + " → " + dstAddrString + ":" + dstPort
+                    + " auto=" + autoKi
+                    + " underpinned=" + underpinnedNetwork);
+            mConnectivityServiceHandler.obtainMessage(CMD_REQUEST_START_KEEPALIVE, autoKi)
+                    .sendToTarget();
+        } catch (InvalidSocketException e) {
+            mKeepaliveTracker.notifyErrorCallback(cb, e.error);
+        }
+    }
+
+    /**
+     * Called when requesting that keepalives be started on a IPsec NAT-T socket. See
+     * {@link android.net.SocketKeepalive}.
+     *
+     * Forward to KeepaliveTracker.
+     **/
+    public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+            @Nullable FileDescriptor fd,
+            int resourceId,
+            int intervalSeconds,
+            @NonNull ISocketKeepaliveCallback cb,
+            @NonNull String srcAddrString,
+            @NonNull String dstAddrString,
+            int dstPort,
+            boolean automaticOnOffKeepalives,
+            @Nullable Network underpinnedNetwork) {
+        final KeepaliveTracker.KeepaliveInfo ki = mKeepaliveTracker.makeNattKeepaliveInfo(nai, fd,
+                resourceId, intervalSeconds, cb, srcAddrString, dstAddrString, dstPort);
+        if (null == ki) return;
+        try {
+            final AutomaticOnOffKeepalive autoKi = new AutomaticOnOffKeepalive(ki,
+                    automaticOnOffKeepalives, underpinnedNetwork);
+            mEventLog.log("Start natt keepalive " + cb + " on " + nai.network
+                    + " " + srcAddrString
+                    + " → " + dstAddrString + ":" + dstPort
+                    + " auto=" + autoKi
+                    + " underpinned=" + underpinnedNetwork);
+            mConnectivityServiceHandler.obtainMessage(CMD_REQUEST_START_KEEPALIVE, autoKi)
+                    .sendToTarget();
+        } catch (InvalidSocketException e) {
+            mKeepaliveTracker.notifyErrorCallback(cb, e.error);
+        }
+    }
+
+    /**
+     * Called by ConnectivityService to start TCP keepalive on a file descriptor.
+     *
+     * In order to offload keepalive for application correctly, sequence number, ack number and
+     * other fields are needed to form the keepalive packet. Thus, this function synchronously
+     * puts the socket into repair mode to get the necessary information. After the socket has been
+     * put into repair mode, the application cannot access the socket until reverted to normal.
+     * See {@link android.net.SocketKeepalive}.
+     *
+     * Forward to KeepaliveTracker.
+     **/
+    public void startTcpKeepalive(@Nullable NetworkAgentInfo nai,
+            @NonNull FileDescriptor fd,
+            int intervalSeconds,
+            @NonNull ISocketKeepaliveCallback cb) {
+        final KeepaliveTracker.KeepaliveInfo ki = mKeepaliveTracker.makeTcpKeepaliveInfo(nai, fd,
+                intervalSeconds, cb);
+        if (null == ki) return;
+        try {
+            final AutomaticOnOffKeepalive autoKi = new AutomaticOnOffKeepalive(ki,
+                    false /* autoOnOff, tcp keepalives are never auto on/off */,
+                    null /* underpinnedNetwork, tcp keepalives do not refer to this */);
+            mConnectivityServiceHandler.obtainMessage(CMD_REQUEST_START_KEEPALIVE, autoKi)
+                    .sendToTarget();
+        } catch (InvalidSocketException e) {
+            mKeepaliveTracker.notifyErrorCallback(cb, e.error);
+        }
+    }
+
+    /**
+     * Dump AutomaticOnOffKeepaliveTracker state.
+     */
+    public void dump(IndentingPrintWriter pw) {
+        mKeepaliveTracker.dump(pw);
+        // Reading DeviceConfig will check if the calling uid and calling package name are the same.
+        // Clear calling identity to align the calling uid and package so that it won't fail if cts
+        // would like to call dump()
+        final boolean featureEnabled = BinderUtils.withCleanCallingIdentity(
+                () -> mDependencies.isFeatureEnabled(AUTOMATIC_ON_OFF_KEEPALIVE_VERSION,
+                        true /* defaultEnabled */));
+        pw.println("AutomaticOnOff enabled: " + featureEnabled);
+        pw.increaseIndent();
+        for (AutomaticOnOffKeepalive autoKi : mAutomaticOnOffKeepalives) {
+            pw.println(autoKi.toString());
+        }
+        pw.decreaseIndent();
+
+        pw.println("Events (most recent first):");
+        pw.increaseIndent();
+        mEventLog.reverseDump(pw);
+        pw.decreaseIndent();
+    }
+
+    /**
+     * Check all keepalives on the network are still valid.
+     *
+     * Forward to KeepaliveTracker.
+     */
+    public void handleCheckKeepalivesStillValid(NetworkAgentInfo nai) {
+        mKeepaliveTracker.handleCheckKeepalivesStillValid(nai);
+    }
+
+    @VisibleForTesting
+    boolean isAnyTcpSocketConnected(int netId) {
+        FileDescriptor fd = null;
+
+        try {
+            fd = mDependencies.createConnectedNetlinkSocket();
+
+            // Get network mask
+            final MarkMaskParcel parcel = mNetd.getFwmarkForNetwork(netId);
+            final int networkMark = (parcel != null) ? parcel.mark : NetlinkUtils.UNKNOWN_MARK;
+            final int networkMask = (parcel != null) ? parcel.mask : NetlinkUtils.NULL_MASK;
+
+            // Send request for each IP family
+            for (final int family : ADDRESS_FAMILIES) {
+                if (isAnyTcpSocketConnectedForFamily(fd, family, networkMark, networkMask)) {
+                    return true;
+                }
+            }
+        } catch (ErrnoException | SocketException | InterruptedIOException | RemoteException e) {
+            Log.e(TAG, "Fail to get socket info via netlink.", e);
+        } finally {
+            SocketUtils.closeSocketQuietly(fd);
+        }
+
+        return false;
+    }
+
+    private boolean isAnyTcpSocketConnectedForFamily(FileDescriptor fd, int family, int networkMark,
+            int networkMask) throws ErrnoException, InterruptedIOException {
+        ensureRunningOnHandlerThread();
+        // Build SocketDiag messages and cache it.
+        if (mSockDiagMsg.get(family) == null) {
+            mSockDiagMsg.put(family, InetDiagMessage.buildInetDiagReqForAliveTcpSockets(family));
+        }
+        mDependencies.sendRequest(fd, mSockDiagMsg.get(family));
+
+        // Iteration limitation as a protection to avoid possible infinite loops.
+        // DEFAULT_RECV_BUFSIZE could read more than 20 sockets per time. Max iteration
+        // should be enough to go through reasonable TCP sockets in the device.
+        final int maxIteration = 100;
+        int parsingIteration = 0;
+        while (parsingIteration < maxIteration) {
+            final ByteBuffer bytes = mDependencies.recvSockDiagResponse(fd);
+
+            try {
+                while (NetlinkUtils.enoughBytesRemainForValidNlMsg(bytes)) {
+                    final int startPos = bytes.position();
+
+                    final int nlmsgLen = bytes.getInt();
+                    final int nlmsgType = bytes.getShort();
+                    if (isEndOfMessageOrError(nlmsgType)) return false;
+                    // TODO: Parse InetDiagMessage to get uid and dst address information to filter
+                    //  socket via NetlinkMessage.parse.
+
+                    // Skip the header to move to data part.
+                    bytes.position(startPos + SOCKDIAG_MSG_HEADER_SIZE);
+
+                    if (isTargetTcpSocket(bytes, nlmsgLen, networkMark, networkMask)) {
+                        if (Log.isLoggable(TAG, Log.DEBUG)) {
+                            bytes.position(startPos);
+                            final InetDiagMessage diagMsg = (InetDiagMessage) NetlinkMessage.parse(
+                                    bytes, OsConstants.NETLINK_INET_DIAG);
+                            Log.d(TAG, String.format("Found open TCP connection by uid %d to %s"
+                                            + " cookie %d",
+                                    diagMsg.inetDiagMsg.idiag_uid,
+                                    diagMsg.inetDiagMsg.id.remSocketAddress,
+                                    diagMsg.inetDiagMsg.id.cookie));
+                        }
+                        return true;
+                    }
+                }
+            } catch (BufferUnderflowException e) {
+                // The exception happens in random place in either header position or any data
+                // position. Partial bytes from the middle of the byte buffer may not be enough to
+                // clarify, so print out the content before the error to possibly prevent printing
+                // the whole 8K buffer.
+                final int exceptionPos = bytes.position();
+                final String hex = HexDump.dumpHexString(bytes.array(), 0, exceptionPos);
+                Log.e(TAG, "Unexpected socket info parsing: " + hex, e);
+            }
+
+            parsingIteration++;
+        }
+        return false;
+    }
+
+    private boolean isEndOfMessageOrError(int nlmsgType) {
+        return nlmsgType == NLMSG_DONE || nlmsgType != SOCK_DIAG_BY_FAMILY;
+    }
+
+    private boolean isTargetTcpSocket(@NonNull ByteBuffer bytes, int nlmsgLen, int networkMark,
+            int networkMask) {
+        final int mark = readSocketDataAndReturnMark(bytes, nlmsgLen);
+        return (mark & networkMask) == networkMark;
+    }
+
+    private int readSocketDataAndReturnMark(@NonNull ByteBuffer bytes, int nlmsgLen) {
+        final int nextMsgOffset = bytes.position() + nlmsgLen - SOCKDIAG_MSG_HEADER_SIZE;
+        int mark = NetlinkUtils.INIT_MARK_VALUE;
+        // Get socket mark
+        // TODO: Add a parsing method in NetlinkMessage.parse to support this to skip the remaining
+        //  data.
+        while (bytes.position() < nextMsgOffset) {
+            final StructNlAttr nlattr = StructNlAttr.parse(bytes);
+            if (nlattr != null && nlattr.nla_type == NetlinkUtils.INET_DIAG_MARK) {
+                mark = nlattr.getValueAsInteger();
+            }
+        }
+        return mark;
+    }
+
+    private void ensureRunningOnHandlerThread() {
+        if (mConnectivityServiceHandler.getLooper().getThread() != Thread.currentThread()) {
+            throw new IllegalStateException(
+                    "Not running on handler thread: " + Thread.currentThread().getName());
+        }
+    }
+
+    private long getTcpPollingIntervalMs(@NonNull AutomaticOnOffKeepalive ki) {
+        final boolean useLowTimer = mTestLowTcpPollingTimerUntilMs > System.currentTimeMillis();
+        // Adjust the polling interval to be smaller than the keepalive delay to preserve
+        // some time for the system to restart the keepalive.
+        final int timer = ki.mKi.getKeepaliveIntervalSec() * 1000 - ADJUST_TCP_POLLING_DELAY_MS;
+        if (timer < MIN_INTERVAL_SEC) {
+            Log.wtf(TAG, "Unreasonably low keepalive delay: " + ki.mKi.getKeepaliveIntervalSec());
+        }
+        return useLowTimer ? LOW_TCP_POLLING_INTERVAL_MS : Math.max(timer, MIN_INTERVAL_SEC);
+    }
+
+    /**
+     * Temporarily use low TCP polling timer for testing.
+     * The value works when the time set is more than {@link System.currentTimeMillis()}.
+     */
+    public void handleSetTestLowTcpPollingTimer(long timeMs) {
+        Log.d(TAG, "handleSetTestLowTcpPollingTimer: " + timeMs);
+        mTestLowTcpPollingTimerUntilMs = timeMs;
+    }
+
+    /**
+     * Dependencies class for testing.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        private final Context mContext;
+
+        public Dependencies(final Context context) {
+            mContext = context;
+        }
+
+        /**
+         * Create a netlink socket connected to the kernel.
+         *
+         * @return fd the fileDescriptor of the socket.
+         */
+        public FileDescriptor createConnectedNetlinkSocket()
+                throws ErrnoException, SocketException {
+            final FileDescriptor fd = NetlinkUtils.createNetLinkInetDiagSocket();
+            NetlinkUtils.connectSocketToNetlink(fd);
+            Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO,
+                    StructTimeval.fromMillis(IO_TIMEOUT_MS));
+            return fd;
+        }
+
+        /**
+         * Send composed message request to kernel.
+         *
+         * The given FileDescriptor is expected to be created by
+         * {@link #createConnectedNetlinkSocket} or equivalent way.
+         *
+         * @param fd a netlink socket {@code FileDescriptor} connected to the kernel.
+         * @param msg the byte array representing the request message to write to kernel.
+         */
+        public void sendRequest(@NonNull final FileDescriptor fd,
+                @NonNull final byte[] msg)
+                throws ErrnoException, InterruptedIOException {
+            Os.write(fd, msg, 0 /* byteOffset */, msg.length);
+        }
+
+        /**
+         * Get an INetd connector.
+         */
+        public INetd getNetd() {
+            return INetd.Stub.asInterface(
+                    (IBinder) mContext.getSystemService(Context.NETD_SERVICE));
+        }
+
+        /**
+         * Get an instance of AlarmManager
+         */
+        public AlarmManager getAlarmManager(@NonNull final Context ctx) {
+            return ctx.getSystemService(AlarmManager.class);
+        }
+
+        /**
+         * Receive the response message from kernel via given {@code FileDescriptor}.
+         * The usage should follow the {@code #sendRequest} call with the same
+         * FileDescriptor.
+         *
+         * The overall response may be large but the individual messages should not be
+         * excessively large(8-16kB) because trying to get the kernel to return
+         * everything in one big buffer is inefficient as it forces the kernel to allocate
+         * large chunks of linearly physically contiguous memory. The usage should iterate the
+         * call of this method until the end of the overall message.
+         *
+         * The default receiving buffer size should be small enough that it is always
+         * processed within the {@link NetlinkUtils#IO_TIMEOUT_MS} timeout.
+         */
+        public ByteBuffer recvSockDiagResponse(@NonNull final FileDescriptor fd)
+                throws ErrnoException, InterruptedIOException {
+            return NetlinkUtils.recvMessage(
+                    fd, NetlinkUtils.DEFAULT_RECV_BUFSIZE, NetlinkUtils.IO_TIMEOUT_MS);
+        }
+
+        /**
+         * Construct a new KeepaliveTracker.
+         */
+        public KeepaliveTracker newKeepaliveTracker(@NonNull Context context,
+                @NonNull Handler connectivityserviceHander) {
+            return new KeepaliveTracker(mContext, connectivityserviceHander);
+        }
+
+        /**
+         * Find out if a feature is enabled from DeviceConfig.
+         *
+         * @param name The name of the property to look up.
+         * @param defaultEnabled whether to consider the feature enabled in the absence of
+         *                       the flag. This MUST be a statically-known constant.
+         * @return whether the feature is enabled
+         */
+        public boolean isFeatureEnabled(@NonNull final String name, final boolean defaultEnabled) {
+            return DeviceConfigUtils.isFeatureEnabled(mContext, NAMESPACE_TETHERING, name,
+                    DeviceConfigUtils.TETHERING_MODULE_NAME, defaultEnabled);
+        }
+
+        /**
+         * Returns milliseconds since boot, including time spent in sleep.
+         *
+         * @return elapsed milliseconds since boot.
+         */
+        public long getElapsedRealtime() {
+            return SystemClock.elapsedRealtime();
+        }
+    }
+}
diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
index b06c8aa..4325763 100644
--- a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
+++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
@@ -38,6 +38,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.HandlerExecutor;
 import com.android.networkstack.apishim.TelephonyManagerShimImpl;
 import com.android.networkstack.apishim.common.TelephonyManagerShim;
 import com.android.networkstack.apishim.common.TelephonyManagerShim.CarrierPrivilegesListenerShim;
@@ -46,7 +47,6 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
-import java.util.concurrent.RejectedExecutionException;
 
 /**
  * Tracks the uid of the carrier privileged app that provides the carrier config.
@@ -105,27 +105,6 @@
     }
 
     /**
-     * An adapter {@link Executor} that posts all executed tasks onto the given
-     * {@link Handler}.
-     *
-     * TODO : migrate to the version in frameworks/libs/net when it's ready
-     *
-     * @hide
-     */
-    public class HandlerExecutor implements Executor {
-        private final Handler mHandler;
-        public HandlerExecutor(@NonNull Handler handler) {
-            mHandler = handler;
-        }
-        @Override
-        public void execute(Runnable command) {
-            if (!mHandler.post(command)) {
-                throw new RejectedExecutionException(mHandler + " is shutting down");
-            }
-        }
-    }
-
-    /**
      * Broadcast receiver for ACTION_MULTI_SIM_CONFIG_CHANGED
      *
      * <p>The broadcast receiver is registered with mHandler
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index 5d04632..fbe706c 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -237,9 +237,8 @@
         /**
          * Stop clatd.
          */
-        public void stopClatd(String iface, String pfx96, String v4, String v6, int pid)
-                throws IOException {
-            native_stopClatd(iface, pfx96, v4, v6, pid);
+        public void stopClatd(int pid) throws IOException {
+            native_stopClatd(pid);
         }
 
         /**
@@ -843,9 +842,7 @@
         Log.i(TAG, "Stopping clatd pid=" + mClatdTracker.pid + " on " + mClatdTracker.iface);
 
         maybeStopBpf(mClatdTracker);
-        mDeps.stopClatd(mClatdTracker.iface, mClatdTracker.pfx96.getHostAddress(),
-                mClatdTracker.v4.getHostAddress(), mClatdTracker.v6.getHostAddress(),
-                mClatdTracker.pid);
+        mDeps.stopClatd(mClatdTracker.pid);
         untagSocket(mClatdTracker.cookie);
 
         Log.i(TAG, "clatd on " + mClatdTracker.iface + " stopped");
@@ -944,7 +941,6 @@
     private static native int native_startClatd(FileDescriptor tunfd, FileDescriptor readsock6,
             FileDescriptor writesock6, String iface, String pfx96, String v4, String v6)
             throws IOException;
-    private static native void native_stopClatd(String iface, String pfx96, String v4, String v6,
-            int pid) throws IOException;
+    private static native void native_stopClatd(int pid) throws IOException;
     private static native long native_getSocketCookie(FileDescriptor sock) throws IOException;
 }
diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java
index 122ea1c..9039a14 100644
--- a/service/src/com/android/server/connectivity/ConnectivityFlags.java
+++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java
@@ -61,6 +61,6 @@
      */
     public void loadFlags(ConnectivityService.Dependencies deps, Context ctx) {
         mNoRematchAllRequestsOnRegister = deps.isFeatureEnabled(
-                ctx, NO_REMATCH_ALL_REQUESTS_ON_REGISTER, false /* defaultEnabled */);
+                ctx, NO_REMATCH_ALL_REQUESTS_ON_REGISTER);
     }
 }
diff --git a/framework/src/android/net/ConnectivityResources.java b/service/src/com/android/server/connectivity/ConnectivityResources.java
similarity index 67%
rename from framework/src/android/net/ConnectivityResources.java
rename to service/src/com/android/server/connectivity/ConnectivityResources.java
index 18f0de0..9089e4a 100644
--- a/framework/src/android/net/ConnectivityResources.java
+++ b/service/src/com/android/server/connectivity/ConnectivityResources.java
@@ -14,22 +14,16 @@
  * limitations under the License.
  */
 
-package android.net;
-
-import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+package com.android.server.connectivity;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.List;
+import com.android.net.module.util.DeviceConfigUtils;
 
 /**
  * Utility to obtain the {@link com.android.server.ConnectivityService} {@link Resources}, in the
@@ -37,10 +31,6 @@
  * @hide
  */
 public class ConnectivityResources {
-    private static final String RESOURCES_APK_INTENT =
-            "com.android.server.connectivity.intent.action.SERVICE_CONNECTIVITY_RESOURCES_APK";
-    private static final String RES_PKG_DIR = "/apex/com.android.tethering/";
-
     @NonNull
     private final Context mContext;
 
@@ -76,21 +66,10 @@
             return mResourcesContext;
         }
 
-        final List<ResolveInfo> pkgs = mContext.getPackageManager()
-                .queryIntentActivities(new Intent(RESOURCES_APK_INTENT), MATCH_SYSTEM_ONLY);
-        pkgs.removeIf(pkg -> !pkg.activityInfo.applicationInfo.sourceDir.startsWith(RES_PKG_DIR));
-        if (pkgs.size() > 1) {
-            Log.wtf(ConnectivityResources.class.getSimpleName(),
-                    "More than one package found: " + pkgs);
-        }
-        if (pkgs.isEmpty()) {
-            throw new IllegalStateException("No connectivity resource package found");
-        }
-
+        final String resPkg = DeviceConfigUtils.getConnectivityResourcesPackageName(mContext);
         final Context pkgContext;
         try {
-            pkgContext = mContext.createPackageContext(
-                    pkgs.get(0).activityInfo.applicationInfo.packageName, 0 /* flags */);
+            pkgContext = mContext.createPackageContext(resPkg, 0 /* flags */);
         } catch (PackageManager.NameNotFoundException e) {
             throw new IllegalStateException("Resolved package not found", e);
         }
diff --git a/service/src/com/android/server/connectivity/DscpPolicyTracker.java b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
index 2bfad10..8d566b6 100644
--- a/service/src/com/android/server/connectivity/DscpPolicyTracker.java
+++ b/service/src/com/android/server/connectivity/DscpPolicyTracker.java
@@ -66,8 +66,8 @@
 
     private Set<String> mAttachedIfaces;
 
-    private final BpfMap<Struct.U32, DscpPolicyValue> mBpfDscpIpv4Policies;
-    private final BpfMap<Struct.U32, DscpPolicyValue> mBpfDscpIpv6Policies;
+    private final BpfMap<Struct.S32, DscpPolicyValue> mBpfDscpIpv4Policies;
+    private final BpfMap<Struct.S32, DscpPolicyValue> mBpfDscpIpv6Policies;
 
     // The actual policy rules used by the BPF code to process packets
     // are in mBpfDscpIpv4Policies and mBpfDscpIpv4Policies. Both of
@@ -85,10 +85,10 @@
     public DscpPolicyTracker() throws ErrnoException {
         mAttachedIfaces = new HashSet<String>();
         mIfaceIndexToPolicyIdBpfMapIndex = new HashMap<Integer, SparseIntArray>();
-        mBpfDscpIpv4Policies = new BpfMap<Struct.U32, DscpPolicyValue>(IPV4_POLICY_MAP_PATH,
-                BpfMap.BPF_F_RDWR, Struct.U32.class, DscpPolicyValue.class);
-        mBpfDscpIpv6Policies = new BpfMap<Struct.U32, DscpPolicyValue>(IPV6_POLICY_MAP_PATH,
-                BpfMap.BPF_F_RDWR, Struct.U32.class, DscpPolicyValue.class);
+        mBpfDscpIpv4Policies = new BpfMap<Struct.S32, DscpPolicyValue>(IPV4_POLICY_MAP_PATH,
+                BpfMap.BPF_F_RDWR, Struct.S32.class, DscpPolicyValue.class);
+        mBpfDscpIpv6Policies = new BpfMap<Struct.S32, DscpPolicyValue>(IPV6_POLICY_MAP_PATH,
+                BpfMap.BPF_F_RDWR, Struct.S32.class, DscpPolicyValue.class);
     }
 
     private boolean isUnusedIndex(int index) {
@@ -181,7 +181,7 @@
             // are both null or if they are both instances of Inet4Address.
             if (matchesIpv4(policy)) {
                 mBpfDscpIpv4Policies.insertOrReplaceEntry(
-                        new Struct.U32(addIndex),
+                        new Struct.S32(addIndex),
                         new DscpPolicyValue(policy.getSourceAddress(),
                             policy.getDestinationAddress(), ifIndex,
                             policy.getSourcePort(), policy.getDestinationPortRange(),
@@ -192,7 +192,7 @@
             // are both null or if they are both instances of Inet6Address.
             if (matchesIpv6(policy)) {
                 mBpfDscpIpv6Policies.insertOrReplaceEntry(
-                        new Struct.U32(addIndex),
+                        new Struct.S32(addIndex),
                         new DscpPolicyValue(policy.getSourceAddress(),
                                 policy.getDestinationAddress(), ifIndex,
                                 policy.getSourcePort(), policy.getDestinationPortRange(),
@@ -258,8 +258,8 @@
             boolean sendCallback) {
         int status = DSCP_POLICY_STATUS_POLICY_NOT_FOUND;
         try {
-            mBpfDscpIpv4Policies.replaceEntry(new Struct.U32(index), DscpPolicyValue.NONE);
-            mBpfDscpIpv6Policies.replaceEntry(new Struct.U32(index), DscpPolicyValue.NONE);
+            mBpfDscpIpv4Policies.replaceEntry(new Struct.S32(index), DscpPolicyValue.NONE);
+            mBpfDscpIpv6Policies.replaceEntry(new Struct.S32(index), DscpPolicyValue.NONE);
             status = DSCP_POLICY_STATUS_DELETED;
         } catch (ErrnoException e) {
             Log.e(TAG, "Failed to delete policy from map: ", e);
diff --git a/service/src/com/android/server/connectivity/FullScore.java b/service/src/com/android/server/connectivity/FullScore.java
index 2303894..87ae0c9 100644
--- a/service/src/com/android/server/connectivity/FullScore.java
+++ b/service/src/com/android/server/connectivity/FullScore.java
@@ -23,7 +23,7 @@
 import static android.net.NetworkScore.KEEP_CONNECTED_NONE;
 import static android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI;
 
-import static com.android.net.module.util.BitUtils.appendStringRepresentationOfBitMaskToStringBuilder;
+import static com.android.net.module.util.BitUtils.describeDifferences;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -338,31 +338,14 @@
 
     /**
      * Returns a short but human-readable string of updates from an older score.
-     * @param old the old capabilities to diff from
+     * @param old the old score to diff from
      * @return a string fit for logging differences, or null if no differences.
-     *         this method cannot return the empty string.
+     *         this method cannot return the empty string. See BitUtils#describeDifferences.
      */
     @Nullable
     public String describeDifferencesFrom(@Nullable final FullScore old) {
         final long oldPolicies = null == old ? 0 : old.mPolicies;
-        final long changed = oldPolicies ^ mPolicies;
-        if (0 == changed) return null;
-        // If the control reaches here, there are changes (additions, removals, or both) so
-        // the code below is guaranteed to add something to the string and can't return "".
-        final long removed = oldPolicies & changed;
-        final long added = mPolicies & changed;
-        final StringBuilder sb = new StringBuilder();
-        if (0 != removed) {
-            sb.append("-");
-            appendStringRepresentationOfBitMaskToStringBuilder(sb, removed,
-                    FullScore::policyNameOf, "-");
-        }
-        if (0 != added) {
-            sb.append("+");
-            appendStringRepresentationOfBitMaskToStringBuilder(sb, added,
-                    FullScore::policyNameOf, "+");
-        }
-        return sb.toString();
+        return describeDifferences(oldPolicies, mPolicies, FullScore::policyNameOf);
     }
 
     // Example output :
diff --git a/Cronet/AndroidManifest.xml b/service/src/com/android/server/connectivity/InvalidTagException.java
similarity index 62%
copy from Cronet/AndroidManifest.xml
copy to service/src/com/android/server/connectivity/InvalidTagException.java
index f3b3c3e..b924d27 100644
--- a/Cronet/AndroidManifest.xml
+++ b/service/src/com/android/server/connectivity/InvalidTagException.java
@@ -1,7 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -15,9 +13,16 @@
  * 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.cronet"
-  android:versionCode="11"
-  android:versionName="R-initial">
-</manifest>
+
+package com.android.server.connectivity;
+
+
+/**
+ * An exception thrown when a Tag is not valid in self_certified_network_capabilities.xml.
+ */
+public class InvalidTagException extends Exception {
+
+    public InvalidTagException(String message) {
+        super(message);
+    }
+}
diff --git a/service/src/com/android/server/connectivity/KeepaliveResourceUtil.java b/service/src/com/android/server/connectivity/KeepaliveResourceUtil.java
new file mode 100644
index 0000000..92b080d
--- /dev/null
+++ b/service/src/com/android/server/connectivity/KeepaliveResourceUtil.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.NetworkCapabilities;
+import android.text.TextUtils;
+import android.util.AndroidRuntimeException;
+
+import com.android.connectivity.resources.R;
+
+/**
+ * Utilities to fetch keepalive configuration from resources.
+ */
+public abstract class KeepaliveResourceUtil {
+
+    /**
+     * Read supported keepalive count for each transport type from overlay resource.
+     *
+     * @param context The context to read resource from.
+     * @return An array of supported keepalive count for each transport type.
+     */
+    @NonNull
+    public static int[] getSupportedKeepalives(@NonNull Context context) {
+        String[] res = null;
+        try {
+            final ConnectivityResources connRes = new ConnectivityResources(context);
+            res = connRes.get().getStringArray(R.array.config_networkSupportedKeepaliveCount);
+        } catch (Resources.NotFoundException unused) {
+        }
+        if (res == null) throw new KeepaliveDeviceConfigurationException("invalid resource");
+
+        final int[] ret = new int[NetworkCapabilities.MAX_TRANSPORT + 1];
+        for (final String row : res) {
+            if (TextUtils.isEmpty(row)) {
+                throw new KeepaliveDeviceConfigurationException("Empty string");
+            }
+            final String[] arr = row.split(",");
+            if (arr.length != 2) {
+                throw new KeepaliveDeviceConfigurationException("Invalid parameter length");
+            }
+
+            int transport;
+            int supported;
+            try {
+                transport = Integer.parseInt(arr[0]);
+                supported = Integer.parseInt(arr[1]);
+            } catch (NumberFormatException e) {
+                throw new KeepaliveDeviceConfigurationException("Invalid number format");
+            }
+
+            if (!NetworkCapabilities.isValidTransport(transport)) {
+                throw new KeepaliveDeviceConfigurationException("Invalid transport " + transport);
+            }
+
+            if (supported < 0) {
+                throw new KeepaliveDeviceConfigurationException(
+                        "Invalid supported count " + supported + " for "
+                                + NetworkCapabilities.transportNameOf(transport));
+            }
+            ret[transport] = supported;
+        }
+        return ret;
+    }
+
+    /**
+     * An exception thrown when the keepalive resource configuration is invalid.
+     */
+    public static class KeepaliveDeviceConfigurationException extends AndroidRuntimeException {
+        public KeepaliveDeviceConfigurationException(final String msg) {
+            super(msg);
+        }
+    }
+}
diff --git a/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
new file mode 100644
index 0000000..290d201
--- /dev/null
+++ b/service/src/com/android/server/connectivity/KeepaliveStatsTracker.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.metrics.DailykeepaliveInfoReported;
+import com.android.metrics.DurationForNumOfKeepalive;
+import com.android.metrics.DurationPerNumOfKeepalive;
+
+import java.util.ArrayList;
+import java.util.List;
+
+// TODO(b/273451360): Also track KeepaliveLifetimeForCarrier and DailykeepaliveInfoReported
+/**
+ * Tracks carrier and duration metrics of automatic on/off keepalives.
+ *
+ * <p>This class follows AutomaticOnOffKeepaliveTracker closely and its on*Keepalive methods needs
+ * to be called in a timely manner to keep the metrics accurate. It is also not thread-safe and all
+ * public methods must be called by the same thread, namely the ConnectivityService handler thread.
+ */
+public class KeepaliveStatsTracker {
+    private static final String TAG = KeepaliveStatsTracker.class.getSimpleName();
+
+    private final Dependencies mDependencies;
+    // List of duration stats metric where the index is the number of concurrent keepalives.
+    // Each DurationForNumOfKeepalive message stores a registered duration and an active duration.
+    // Registered duration is the total time spent with mNumRegisteredKeepalive == index.
+    // Active duration is the total time spent with mNumActiveKeepalive == index.
+    private final List<DurationForNumOfKeepalive.Builder> mDurationPerNumOfKeepalive =
+            new ArrayList<>();
+
+    private int mNumRegisteredKeepalive = 0;
+    private int mNumActiveKeepalive = 0;
+
+    // A timestamp of the most recent time the duration metrics was updated.
+    private long mTimestampSinceLastUpdateDurations;
+
+    /** Dependency class */
+    @VisibleForTesting
+    public static class Dependencies {
+        // Returns a timestamp with the time base of SystemClock.uptimeMillis to keep durations
+        // relative to start time and avoid timezone change.
+        public long getUptimeMillis() {
+            return SystemClock.uptimeMillis();
+        }
+    }
+
+    public KeepaliveStatsTracker() {
+        this(new Dependencies());
+    }
+
+    @VisibleForTesting
+    public KeepaliveStatsTracker(Dependencies dependencies) {
+        mDependencies = dependencies;
+        mTimestampSinceLastUpdateDurations = mDependencies.getUptimeMillis();
+    }
+
+    /** Ensures the list of duration metrics is large enough for number of registered keepalives. */
+    private void ensureDurationPerNumOfKeepaliveSize() {
+        if (mNumActiveKeepalive < 0 || mNumRegisteredKeepalive < 0) {
+            throw new IllegalStateException(
+                    "Number of active or registered keepalives is negative");
+        }
+        if (mNumActiveKeepalive > mNumRegisteredKeepalive) {
+            throw new IllegalStateException(
+                    "Number of active keepalives greater than registered keepalives");
+        }
+
+        while (mDurationPerNumOfKeepalive.size() <= mNumRegisteredKeepalive) {
+            final DurationForNumOfKeepalive.Builder durationForNumOfKeepalive =
+                    DurationForNumOfKeepalive.newBuilder();
+            durationForNumOfKeepalive.setNumOfKeepalive(mDurationPerNumOfKeepalive.size());
+            durationForNumOfKeepalive.setKeepaliveRegisteredDurationsMsec(0);
+            durationForNumOfKeepalive.setKeepaliveActiveDurationsMsec(0);
+
+            mDurationPerNumOfKeepalive.add(durationForNumOfKeepalive);
+        }
+    }
+
+    /**
+     * Updates the durations metrics to the given time. This should always be called before making a
+     * change to mNumRegisteredKeepalive or mNumActiveKeepalive to keep the duration metrics
+     * correct.
+     *
+     * @param timeNow a timestamp obtained using Dependencies.getUptimeMillis
+     */
+    private void updateDurationsPerNumOfKeepalive(long timeNow) {
+        if (mDurationPerNumOfKeepalive.size() < mNumRegisteredKeepalive) {
+            Log.e(TAG, "Unexpected jump in number of registered keepalive");
+        }
+        ensureDurationPerNumOfKeepaliveSize();
+
+        final int durationIncrease = (int) (timeNow - mTimestampSinceLastUpdateDurations);
+        final DurationForNumOfKeepalive.Builder durationForNumOfRegisteredKeepalive =
+                mDurationPerNumOfKeepalive.get(mNumRegisteredKeepalive);
+
+        durationForNumOfRegisteredKeepalive.setKeepaliveRegisteredDurationsMsec(
+                durationForNumOfRegisteredKeepalive.getKeepaliveRegisteredDurationsMsec()
+                        + durationIncrease);
+
+        final DurationForNumOfKeepalive.Builder durationForNumOfActiveKeepalive =
+                mDurationPerNumOfKeepalive.get(mNumActiveKeepalive);
+
+        durationForNumOfActiveKeepalive.setKeepaliveActiveDurationsMsec(
+                durationForNumOfActiveKeepalive.getKeepaliveActiveDurationsMsec()
+                        + durationIncrease);
+
+        mTimestampSinceLastUpdateDurations = timeNow;
+    }
+
+    /** Inform the KeepaliveStatsTracker a keepalive has just started and is active. */
+    public void onStartKeepalive() {
+        final long timeNow = mDependencies.getUptimeMillis();
+        updateDurationsPerNumOfKeepalive(timeNow);
+
+        mNumRegisteredKeepalive++;
+        mNumActiveKeepalive++;
+    }
+
+    /** Inform the KeepaliveStatsTracker a keepalive has just been paused. */
+    public void onPauseKeepalive() {
+        final long timeNow = mDependencies.getUptimeMillis();
+        updateDurationsPerNumOfKeepalive(timeNow);
+
+        mNumActiveKeepalive--;
+    }
+
+    /** Inform the KeepaliveStatsTracker a keepalive has just been resumed. */
+    public void onResumeKeepalive() {
+        final long timeNow = mDependencies.getUptimeMillis();
+        updateDurationsPerNumOfKeepalive(timeNow);
+
+        mNumActiveKeepalive++;
+    }
+
+    /** Inform the KeepaliveStatsTracker a keepalive has just been stopped. */
+    public void onStopKeepalive(boolean wasActive) {
+        final long timeNow = mDependencies.getUptimeMillis();
+        updateDurationsPerNumOfKeepalive(timeNow);
+
+        mNumRegisteredKeepalive--;
+        if (wasActive) mNumActiveKeepalive--;
+    }
+
+    /**
+     * Builds and returns DailykeepaliveInfoReported proto.
+     */
+    public DailykeepaliveInfoReported buildKeepaliveMetrics() {
+        final long timeNow = mDependencies.getUptimeMillis();
+        updateDurationsPerNumOfKeepalive(timeNow);
+
+        final DurationPerNumOfKeepalive.Builder durationPerNumOfKeepalive =
+                DurationPerNumOfKeepalive.newBuilder();
+
+        mDurationPerNumOfKeepalive.forEach(
+                durationForNumOfKeepalive ->
+                        durationPerNumOfKeepalive.addDurationForNumOfKeepalive(
+                                durationForNumOfKeepalive));
+
+        final DailykeepaliveInfoReported.Builder dailyKeepaliveInfoReported =
+                DailykeepaliveInfoReported.newBuilder();
+
+        // TODO(b/273451360): fill all the other values and write to ConnectivityStatsLog.
+        dailyKeepaliveInfoReported.setDurationPerNumOfKeepalive(durationPerNumOfKeepalive);
+
+        return dailyKeepaliveInfoReported.build();
+    }
+
+    /** Resets the stored metrics but maintains the state of keepalives */
+    public void resetMetrics() {
+        mDurationPerNumOfKeepalive.clear();
+        ensureDurationPerNumOfKeepaliveSize();
+    }
+}
diff --git a/service/src/com/android/server/connectivity/KeepaliveTracker.java b/service/src/com/android/server/connectivity/KeepaliveTracker.java
index 3b58823..cc226ce 100644
--- a/service/src/com/android/server/connectivity/KeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/KeepaliveTracker.java
@@ -18,7 +18,6 @@
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.NattSocketKeepalive.NATT_PORT;
-import static android.net.NetworkAgent.CMD_START_SOCKET_KEEPALIVE;
 import static android.net.SocketKeepalive.BINDER_DIED;
 import static android.net.SocketKeepalive.DATA_RECEIVED;
 import static android.net.SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES;
@@ -33,11 +32,11 @@
 import static android.net.SocketKeepalive.MIN_INTERVAL_SEC;
 import static android.net.SocketKeepalive.NO_KEEPALIVE;
 import static android.net.SocketKeepalive.SUCCESS;
+import static android.net.SocketKeepalive.SUCCESS_PAUSED;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.net.ConnectivityResources;
 import android.net.ISocketKeepaliveCallback;
 import android.net.InetAddresses;
 import android.net.InvalidPacketException;
@@ -50,7 +49,6 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
 import android.system.ErrnoException;
@@ -59,6 +57,7 @@
 import android.util.Pair;
 
 import com.android.connectivity.resources.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.net.module.util.HexDump;
 import com.android.net.module.util.IpUtils;
@@ -88,7 +87,6 @@
     /** Keeps track of keepalive requests. */
     private final HashMap <NetworkAgentInfo, HashMap<Integer, KeepaliveInfo>> mKeepalives =
             new HashMap<> ();
-    private final Handler mConnectivityServiceHandler;
     @NonNull
     private final TcpKeepaliveController mTcpController;
     @NonNull
@@ -109,10 +107,10 @@
     private final int mAllowedUnprivilegedSlotsForUid;
 
     public KeepaliveTracker(Context context, Handler handler) {
-        mConnectivityServiceHandler = handler;
         mTcpController = new TcpKeepaliveController(handler);
         mContext = context;
-        mSupportedKeepalives = KeepaliveUtils.getSupportedKeepalives(mContext);
+
+        mSupportedKeepalives = KeepaliveResourceUtil.getSupportedKeepalives(context);
 
         final ConnectivityResources res = new ConnectivityResources(mContext);
         mReservedPrivilegedSlots = res.get().getInteger(
@@ -127,15 +125,20 @@
      * All information about this keepalive is known at construction time except the slot number,
      * which is only returned when the hardware has successfully started the keepalive.
      */
-    class KeepaliveInfo implements IBinder.DeathRecipient {
+    @VisibleForTesting
+    public class KeepaliveInfo implements IBinder.DeathRecipient {
+        // TODO : remove this member. Only AutoOnOffKeepalive should have a reference to this.
+        public final ISocketKeepaliveCallback mCallback;
         // Bookkeeping data.
-        private final ISocketKeepaliveCallback mCallback;
         private final int mUid;
         private final int mPid;
         private final boolean mPrivileged;
-        private final NetworkAgentInfo mNai;
+        public final NetworkAgentInfo mNai;
         private final int mType;
-        private final FileDescriptor mFd;
+        public final FileDescriptor mFd;
+        // True if this was resumed from a previously turned off keepalive, otherwise false.
+        // This is necessary to send the correct callbacks.
+        public final boolean mResumed;
 
         public static final int TYPE_NATT = 1;
         public static final int TYPE_TCP = 2;
@@ -162,6 +165,16 @@
                 int interval,
                 int type,
                 @Nullable FileDescriptor fd) throws InvalidSocketException {
+            this(callback, nai, packet, interval, type, fd, false /* resumed */);
+        }
+
+        KeepaliveInfo(@NonNull ISocketKeepaliveCallback callback,
+                @NonNull NetworkAgentInfo nai,
+                @NonNull KeepalivePacketData packet,
+                int interval,
+                int type,
+                @Nullable FileDescriptor fd,
+                boolean resumed) throws InvalidSocketException {
             mCallback = callback;
             mPid = Binder.getCallingPid();
             mUid = Binder.getCallingUid();
@@ -171,6 +184,7 @@
             mPacket = packet;
             mInterval = interval;
             mType = type;
+            mResumed = resumed;
 
             // For SocketKeepalive, a dup of fd is kept in mFd so the source port from which the
             // keepalives are sent cannot be reused by another app even if the fd gets closed by
@@ -234,6 +248,8 @@
 
         /** Called when the application process is killed. */
         public void binderDied() {
+            // TODO b/267106526 : this is not called on the handler thread but stop() happily
+            // assumes it is, which means this is a pretty dangerous race condition.
             stop(BINDER_DIED);
         }
 
@@ -243,6 +259,14 @@
             }
         }
 
+        public int getSlot() {
+            return mSlot;
+        }
+
+        int getKeepaliveIntervalSec() {
+            return mInterval;
+        }
+
         private int checkNetworkConnected() {
             if (!mNai.networkInfo.isConnectedOrConnecting()) {
                 return ERROR_INVALID_NETWORK;
@@ -325,6 +349,10 @@
         }
 
         void start(int slot) {
+            // BINDER_DIED can happen if the binder died before the KeepaliveInfo was created and
+            // the constructor set the state to BINDER_DIED. If that's the case, the KI is already
+            // cleaned up.
+            if (BINDER_DIED == mStartedState) return;
             mSlot = slot;
             int error = isValid();
             if (error == SUCCESS) {
@@ -369,7 +397,10 @@
             // To prevent races from re-entrance of stop(), return if the state is already stopping.
             // This might happen if multiple event sources stop keepalive in a short time. Such as
             // network disconnect after user calls stop(), or tear down socket after binder died.
-            if (mStartedState == STOPPING) return;
+            // Note that it's always possible this method is called by the auto keepalive timer
+            // or any other way after the binder died, hence the check for BINDER_DIED. If the
+            // binder has died, then the KI has already been cleaned up.
+            if (mStartedState == STOPPING || mStartedState == BINDER_DIED) return;
 
             // Store the reason of stopping, and report it after the keepalive is fully stopped.
             if (mStopReason != ERROR_STOP_REASON_UNINITIALIZED) {
@@ -380,9 +411,10 @@
                     + ": " + reason);
             switch (mStartedState) {
                 case NOT_STARTED:
-                    // Remove the reference of the keepalive that meet error before starting,
+                    // Remove the reference to this keepalive that had an error before starting,
                     // e.g. invalid parameter.
                     cleanupStoppedKeepalive(mNai, mSlot);
+                    if (BINDER_DIED == reason) mStartedState = BINDER_DIED;
                     break;
                 default:
                     mStartedState = STOPPING;
@@ -415,6 +447,14 @@
         void onFileDescriptorInitiatedStop(final int socketKeepaliveReason) {
             handleStopKeepalive(mNai, mSlot, socketKeepaliveReason);
         }
+
+        /**
+         * Construct a new KeepaliveInfo from existing KeepaliveInfo with a new fd.
+         */
+        public KeepaliveInfo withFd(@NonNull FileDescriptor fd) throws InvalidSocketException {
+            return new KeepaliveInfo(mCallback, mNai, mPacket, mInterval, mType, fd,
+                    true /* resumed */);
+        }
     }
 
     void notifyErrorCallback(ISocketKeepaliveCallback cb, int error) {
@@ -444,8 +484,10 @@
         return slot;
     }
 
-    public void handleStartKeepalive(Message message) {
-        KeepaliveInfo ki = (KeepaliveInfo) message.obj;
+    /**
+     * Handle start keepalives with the message.
+     */
+    public void handleStartKeepalive(KeepaliveInfo ki) {
         NetworkAgentInfo nai = ki.getNai();
         int slot = findFirstFreeSlot(nai);
         mKeepalives.get(nai).put(slot, ki);
@@ -512,6 +554,12 @@
             } catch (RemoteException e) {
                 Log.w(TAG, "Discarded onStop callback: " + reason);
             }
+        } else if (reason == SUCCESS_PAUSED) {
+            try {
+                ki.mCallback.onPaused();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Discarded onPaused callback: " + reason);
+            }
         } else if (reason == DATA_RECEIVED) {
             try {
                 ki.mCallback.onDataReceived();
@@ -521,7 +569,20 @@
         } else if (reason == ERROR_STOP_REASON_UNINITIALIZED) {
             throw new IllegalStateException("Unexpected stop reason: " + reason);
         } else if (reason == ERROR_NO_SUCH_SLOT) {
-            throw new IllegalStateException("No such slot: " + reason);
+            // There are multiple independent reasons a keepalive can stop. Some
+            // are software (e.g. the app stops the keepalive) and some are hardware
+            // (e.g. the SIM card gets removed). Therefore, there is a very low
+            // probability that both of these happen at the same time, which would
+            // result in the first stop attempt returning SUCCESS and the second
+            // stop attempt returning NO_SUCH_SLOT. Such a race condition can be
+            // ignored with a log.
+            // This should still be reported because if it happens with any frequency
+            // it probably means there is a bug where the system server is trying
+            // to use a non-existing hardware slot.
+            // TODO : separate the non-existing hardware slot from the case where
+            // there is no keepalive running on this slot.
+            Log.wtf(TAG, "Keepalive on slot " + slot + " can't be stopped : " + reason);
+            notifyErrorCallback(ki.mCallback, reason);
         } else {
             notifyErrorCallback(ki.mCallback, reason);
         }
@@ -529,6 +590,25 @@
         ki.unlinkDeathRecipient();
     }
 
+    /**
+     * Finalize a paused keepalive.
+     *
+     * This will simply send the onStopped() callback after checking that this keepalive is
+     * indeed paused.
+     *
+     * @param ki the keepalive to finalize
+     */
+    public void finalizePausedKeepalive(@NonNull final KeepaliveInfo ki) {
+        if (SUCCESS_PAUSED != ki.mStopReason) {
+            throw new IllegalStateException("Keepalive is not paused");
+        }
+        try {
+            ki.mCallback.onStopped();
+        } catch (RemoteException e) {
+            Log.w(TAG, "Discarded onStopped callback while finalizing paused keepalive");
+        }
+    }
+
     public void handleCheckKeepalivesStillValid(NetworkAgentInfo nai) {
         HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
         if (networkKeepalives != null) {
@@ -578,9 +658,14 @@
                 Log.d(TAG, "Started keepalive " + slot + " on " + nai.toShortString());
                 ki.mStartedState = KeepaliveInfo.STARTED;
                 try {
-                    ki.mCallback.onStarted(slot);
+                    if (ki.mResumed) {
+                        ki.mCallback.onResumed();
+                    } else {
+                        ki.mCallback.onStarted();
+                    }
                 } catch (RemoteException e) {
-                    Log.w(TAG, "Discarded onStarted(" + slot + ") callback");
+                    Log.w(TAG, "Discarded " + (ki.mResumed ? "onResumed" : "onStarted")
+                            + " callback for slot " + slot);
                 }
             } else {
                 Log.d(TAG, "Failed to start keepalive " + slot + " on " + nai.toShortString()
@@ -604,7 +689,8 @@
      * Called when requesting that keepalives be started on a IPsec NAT-T socket. See
      * {@link android.net.SocketKeepalive}.
      **/
-    public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+    @Nullable
+    public KeepaliveInfo makeNattKeepaliveInfo(@Nullable NetworkAgentInfo nai,
             @Nullable FileDescriptor fd,
             int intervalSeconds,
             @NonNull ISocketKeepaliveCallback cb,
@@ -614,7 +700,7 @@
             int dstPort) {
         if (nai == null) {
             notifyErrorCallback(cb, ERROR_INVALID_NETWORK);
-            return;
+            return null;
         }
 
         InetAddress srcAddress, dstAddress;
@@ -623,7 +709,7 @@
             dstAddress = InetAddresses.parseNumericAddress(dstAddrString);
         } catch (IllegalArgumentException e) {
             notifyErrorCallback(cb, ERROR_INVALID_IP_ADDRESS);
-            return;
+            return null;
         }
 
         KeepalivePacketData packet;
@@ -632,7 +718,7 @@
                     srcAddress, srcPort, dstAddress, NATT_PORT);
         } catch (InvalidPacketException e) {
             notifyErrorCallback(cb, e.getError());
-            return;
+            return null;
         }
         KeepaliveInfo ki = null;
         try {
@@ -641,15 +727,14 @@
         } catch (InvalidSocketException | IllegalArgumentException | SecurityException e) {
             Log.e(TAG, "Fail to construct keepalive", e);
             notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
-            return;
+            return null;
         }
-        Log.d(TAG, "Created keepalive: " + ki.toString());
-        mConnectivityServiceHandler.obtainMessage(
-                NetworkAgent.CMD_START_SOCKET_KEEPALIVE, ki).sendToTarget();
+        Log.d(TAG, "Created keepalive: " + ki);
+        return ki;
     }
 
     /**
-     * Called by ConnectivityService to start TCP keepalive on a file descriptor.
+     * Make a KeepaliveInfo for a TCP socket.
      *
      * In order to offload keepalive for application correctly, sequence number, ack number and
      * other fields are needed to form the keepalive packet. Thus, this function synchronously
@@ -658,13 +743,14 @@
      *
      * See {@link android.net.SocketKeepalive}.
      **/
-    public void startTcpKeepalive(@Nullable NetworkAgentInfo nai,
+    @Nullable
+    public KeepaliveInfo makeTcpKeepaliveInfo(@Nullable NetworkAgentInfo nai,
             @NonNull FileDescriptor fd,
             int intervalSeconds,
             @NonNull ISocketKeepaliveCallback cb) {
         if (nai == null) {
             notifyErrorCallback(cb, ERROR_INVALID_NETWORK);
-            return;
+            return null;
         }
 
         final TcpKeepalivePacketData packet;
@@ -672,10 +758,10 @@
             packet = TcpKeepaliveController.getTcpKeepalivePacket(fd);
         } catch (InvalidSocketException e) {
             notifyErrorCallback(cb, e.error);
-            return;
+            return null;
         } catch (InvalidPacketException e) {
             notifyErrorCallback(cb, e.getError());
-            return;
+            return null;
         }
         KeepaliveInfo ki = null;
         try {
@@ -684,20 +770,22 @@
         } catch (InvalidSocketException | IllegalArgumentException | SecurityException e) {
             Log.e(TAG, "Fail to construct keepalive e=" + e);
             notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
-            return;
+            return null;
         }
         Log.d(TAG, "Created keepalive: " + ki.toString());
-        mConnectivityServiceHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, ki).sendToTarget();
+        return ki;
     }
 
-   /**
-    * Called when requesting that keepalives be started on a IPsec NAT-T socket. This function is
-    * identical to {@link #startNattKeepalive}, but also takes a {@code resourceId}, which is the
-    * resource index bound to the {@link UdpEncapsulationSocket} when creating by
-    * {@link com.android.server.IpSecService} to verify whether the given
-    * {@link UdpEncapsulationSocket} is legitimate.
-    **/
-    public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+    /**
+     * Make a KeepaliveInfo for an IPSec NAT-T socket.
+     *
+     * This function is identical to {@link #makeNattKeepaliveInfo}, but also takes a
+     * {@code resourceId}, which is the resource index bound to the {@link UdpEncapsulationSocket}
+     * when creating by {@link com.android.server.IpSecService} to verify whether the given
+     * {@link UdpEncapsulationSocket} is legitimate.
+     **/
+    @Nullable
+    public KeepaliveInfo makeNattKeepaliveInfo(@Nullable NetworkAgentInfo nai,
             @Nullable FileDescriptor fd,
             int resourceId,
             int intervalSeconds,
@@ -708,6 +796,7 @@
         // Ensure that the socket is created by IpSecService.
         if (!isNattKeepaliveSocketValid(fd, resourceId)) {
             notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
+            return null;
         }
 
         // Get src port to adopt old API.
@@ -717,10 +806,11 @@
             srcPort = ((InetSocketAddress) srcSockAddr).getPort();
         } catch (ErrnoException e) {
             notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
+            return null;
         }
 
         // Forward request to old API.
-        startNattKeepalive(nai, fd, intervalSeconds, cb, srcAddrString, srcPort,
+        return makeNattKeepaliveInfo(nai, fd, intervalSeconds, cb, srcAddrString, srcPort,
                 dstAddrString, dstPort);
     }
 
@@ -739,6 +829,9 @@
         return true;
     }
 
+    /**
+     * Dump KeepaliveTracker state.
+     */
     public void dump(IndentingPrintWriter pw) {
         pw.println("Supported Socket keepalives: " + Arrays.toString(mSupportedKeepalives));
         pw.println("Reserved Privileged keepalives: " + mReservedPrivilegedSlots);
diff --git a/service/src/com/android/server/connectivity/LingerMonitor.java b/service/src/com/android/server/connectivity/LingerMonitor.java
index df34ce7..8503fcc 100644
--- a/service/src/com/android/server/connectivity/LingerMonitor.java
+++ b/service/src/com/android/server/connectivity/LingerMonitor.java
@@ -25,7 +25,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.net.ConnectivityResources;
 import android.net.NetworkCapabilities;
 import android.os.SystemClock;
 import android.os.UserHandle;
diff --git a/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java b/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
index 58196f7..93018bb 100644
--- a/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
+++ b/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
@@ -28,7 +28,6 @@
 import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.database.ContentObserver;
-import android.net.ConnectivityResources;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Handler;
diff --git a/service/src/com/android/server/connectivity/NetworkDiagnostics.java b/service/src/com/android/server/connectivity/NetworkDiagnostics.java
index 509110d..4f80d47 100644
--- a/service/src/com/android/server/connectivity/NetworkDiagnostics.java
+++ b/service/src/com/android/server/connectivity/NetworkDiagnostics.java
@@ -18,6 +18,12 @@
 
 import static android.system.OsConstants.*;
 
+import static com.android.net.module.util.NetworkStackConstants.DNS_OVER_TLS_PORT;
+import static com.android.net.module.util.NetworkStackConstants.ICMP_HEADER_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.InetAddresses;
@@ -33,6 +39,7 @@
 import android.system.Os;
 import android.system.StructTimeval;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.Pair;
 
 import com.android.internal.util.IndentingPrintWriter;
@@ -172,7 +179,7 @@
         }
     }
 
-    private final Map<InetAddress, Measurement> mIcmpChecks = new HashMap<>();
+    private final Map<Pair<InetAddress, Integer>, Measurement> mIcmpChecks = new HashMap<>();
     private final Map<Pair<InetAddress, InetAddress>, Measurement> mExplicitSourceIcmpChecks =
             new HashMap<>();
     private final Map<InetAddress, Measurement> mDnsUdpChecks = new HashMap<>();
@@ -205,17 +212,21 @@
             mLinkProperties.addDnsServer(TEST_DNS6);
         }
 
+        final int mtu = mLinkProperties.getMtu();
         for (RouteInfo route : mLinkProperties.getRoutes()) {
             if (route.getType() == RouteInfo.RTN_UNICAST && route.hasGateway()) {
                 InetAddress gateway = route.getGateway();
-                prepareIcmpMeasurement(gateway);
+                // Use mtu in the route if exists. Otherwise, use the one in the link property.
+                final int routeMtu = route.getMtu();
+                prepareIcmpMeasurements(gateway, (routeMtu > 0) ? routeMtu : mtu);
                 if (route.isIPv6Default()) {
                     prepareExplicitSourceIcmpMeasurements(gateway);
                 }
             }
         }
+
         for (InetAddress nameserver : mLinkProperties.getDnsServers()) {
-            prepareIcmpMeasurement(nameserver);
+            prepareIcmpMeasurements(nameserver, mtu);
             prepareDnsMeasurement(nameserver);
 
             // Unlike the DnsResolver which doesn't do certificate validation in opportunistic mode,
@@ -261,11 +272,50 @@
                 localAddr.getHostAddress(), inetSockAddr.getPort());
     }
 
-    private void prepareIcmpMeasurement(InetAddress target) {
-        if (!mIcmpChecks.containsKey(target)) {
-            Measurement measurement = new Measurement();
-            measurement.thread = new Thread(new IcmpCheck(target, measurement));
-            mIcmpChecks.put(target, measurement);
+    private static int getHeaderLen(@NonNull InetAddress target) {
+        // Convert IPv4 mapped v6 address to v4 if any.
+        try {
+            final InetAddress addr = InetAddress.getByAddress(target.getAddress());
+            // An ICMPv6 header is technically 4 bytes, but the implementation in IcmpCheck#run()
+            // will always fill in another 4 bytes padding in the v6 diagnostic packets, so the size
+            // before icmp data is always 8 bytes in the implementation of ICMP diagnostics for both
+            // v4 and v6 packets. Thus, it's fine to use the v4 header size in the length
+            // calculation.
+            if (addr instanceof Inet6Address) {
+                return IPV6_HEADER_LEN + ICMP_HEADER_LEN;
+            }
+        } catch (UnknownHostException e) {
+            Log.e(TAG, "Create InetAddress fail(" + target + "): " + e);
+        }
+
+        return IPV4_HEADER_MIN_LEN + ICMP_HEADER_LEN;
+    }
+
+    private void prepareIcmpMeasurements(@NonNull InetAddress target, int targetNetworkMtu) {
+        // Test with different size payload ICMP.
+        // 1. Test with 0 payload.
+        addPayloadIcmpMeasurement(target, 0);
+        final int header = getHeaderLen(target);
+        // 2. Test with full size MTU.
+        addPayloadIcmpMeasurement(target, targetNetworkMtu - header);
+        // 3. If v6, make another measurement with the full v6 min MTU, unless that's what
+        //    was done above.
+        if ((target instanceof Inet6Address) && (targetNetworkMtu != IPV6_MIN_MTU)) {
+            addPayloadIcmpMeasurement(target, IPV6_MIN_MTU - header);
+        }
+    }
+
+    private void addPayloadIcmpMeasurement(@NonNull InetAddress target, int payloadLen) {
+        // This can happen if the there is no mtu filled(which is 0) in the link property.
+        // The value becomes negative after minus header length.
+        if (payloadLen < 0) return;
+
+        final Pair<InetAddress, Integer> lenTarget =
+                new Pair<>(target, Integer.valueOf(payloadLen));
+        if (!mIcmpChecks.containsKey(lenTarget)) {
+            final Measurement measurement = new Measurement();
+            measurement.thread = new Thread(new IcmpCheck(target, payloadLen, measurement));
+            mIcmpChecks.put(lenTarget, measurement);
         }
     }
 
@@ -276,7 +326,7 @@
                 Pair<InetAddress, InetAddress> srcTarget = new Pair<>(source, target);
                 if (!mExplicitSourceIcmpChecks.containsKey(srcTarget)) {
                     Measurement measurement = new Measurement();
-                    measurement.thread = new Thread(new IcmpCheck(source, target, measurement));
+                    measurement.thread = new Thread(new IcmpCheck(source, target, 0, measurement));
                     mExplicitSourceIcmpChecks.put(srcTarget, measurement);
                 }
             }
@@ -334,8 +384,8 @@
         ArrayList<Measurement> measurements = new ArrayList(totalMeasurementCount());
 
         // Sort measurements IPv4 first.
-        for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
-            if (entry.getKey() instanceof Inet4Address) {
+        for (Map.Entry<Pair<InetAddress, Integer>, Measurement> entry : mIcmpChecks.entrySet()) {
+            if (entry.getKey().first instanceof Inet4Address) {
                 measurements.add(entry.getValue());
             }
         }
@@ -357,8 +407,8 @@
         }
 
         // IPv6 measurements second.
-        for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
-            if (entry.getKey() instanceof Inet6Address) {
+        for (Map.Entry<Pair<InetAddress, Integer>, Measurement> entry : mIcmpChecks.entrySet()) {
+            if (entry.getKey().first instanceof Inet6Address) {
                 measurements.add(entry.getValue());
             }
         }
@@ -489,8 +539,11 @@
         private static final int PACKET_BUFSIZE = 512;
         private final int mProtocol;
         private final int mIcmpType;
+        private final int mPayloadSize;
+        // The length parameter is effectively the -s flag to ping/ping6 to specify the number of
+        // data bytes to be sent.
+        IcmpCheck(InetAddress source, InetAddress target, int length, Measurement measurement) {
 
-        public IcmpCheck(InetAddress source, InetAddress target, Measurement measurement) {
             super(source, target, measurement);
 
             if (mAddressFamily == AF_INET6) {
@@ -502,12 +555,13 @@
                 mIcmpType = NetworkConstants.ICMPV4_ECHO_REQUEST_TYPE;
                 mMeasurement.description = "ICMPv4";
             }
-
-            mMeasurement.description += " dst{" + mTarget.getHostAddress() + "}";
+            mPayloadSize = length;
+            mMeasurement.description += " payloadLength{" + mPayloadSize  + "}"
+                    + " dst{" + mTarget.getHostAddress() + "}";
         }
 
-        public IcmpCheck(InetAddress target, Measurement measurement) {
-            this(null, target, measurement);
+        IcmpCheck(InetAddress target, int length, Measurement measurement) {
+            this(null, target, length, measurement);
         }
 
         @Override
@@ -523,9 +577,11 @@
             mMeasurement.description += " src{" + socketAddressToString(mSocketAddress) + "}";
 
             // Build a trivial ICMP packet.
-            final byte[] icmpPacket = {
-                    (byte) mIcmpType, 0, 0, 0, 0, 0, 0, 0  // ICMP header
-            };
+            // The v4 ICMP header ICMP_HEADER_LEN (which is 8) and v6 is only 4 bytes (4 bytes
+            // message body followed by header before the payload).
+            // Use 8 bytes for both v4 and v6 for simplicity.
+            final byte[] icmpPacket = new byte[ICMP_HEADER_LEN + mPayloadSize];
+            icmpPacket[0] = (byte) mIcmpType;
 
             int count = 0;
             mMeasurement.startTime = now();
@@ -675,7 +731,6 @@
     private class DnsTlsCheck extends DnsUdpCheck {
         private static final int TCP_CONNECT_TIMEOUT_MS = 2500;
         private static final int TCP_TIMEOUT_MS = 2000;
-        private static final int DNS_TLS_PORT = 853;
         private static final int DNS_HEADER_SIZE = 12;
 
         private final String mHostname;
@@ -714,7 +769,8 @@
             final byte[] dnsPacket = getDnsQueryPacket(sixRandomDigits);
 
             mMeasurement.startTime = now();
-            sslSocket.connect(new InetSocketAddress(mTarget, DNS_TLS_PORT), TCP_CONNECT_TIMEOUT_MS);
+            sslSocket.connect(new InetSocketAddress(mTarget, DNS_OVER_TLS_PORT),
+                    TCP_CONNECT_TIMEOUT_MS);
 
             // Synchronous call waiting for the TLS handshake complete.
             sslSocket.startHandshake();
diff --git a/service/src/com/android/server/connectivity/NetworkNotificationManager.java b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
index 45da0ea..8b0cb7c 100644
--- a/service/src/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
@@ -22,6 +22,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
 import android.annotation.NonNull;
+import android.app.ActivityOptions;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -29,10 +30,11 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.drawable.Icon;
-import android.net.ConnectivityResources;
 import android.net.NetworkSpecifier;
 import android.net.TelephonyNetworkSpecifier;
 import android.net.wifi.WifiInfo;
+import android.os.Build;
+import android.os.Bundle;
 import android.os.UserHandle;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -45,6 +47,7 @@
 import com.android.connectivity.resources.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.modules.utils.build.SdkLevel;
 
 public class NetworkNotificationManager {
 
@@ -328,7 +331,26 @@
         }
 
         try {
-            intent.send();
+            Bundle options = null;
+
+            if (SdkLevel.isAtLeastU() && intent.isActivity()) {
+                // Also check SDK_INT >= T separately, as the linter in some T-based branches does
+                // not recognize "isAtLeastU && something" as an SDK check for T+ APIs.
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+                    // Android U requires pending intent background start mode to be specified:
+                    // See #background-activity-restrictions in
+                    // https://developer.android.com/about/versions/14/behavior-changes-14
+                    // But setPendingIntentBackgroundActivityStartMode is U+, and replaces
+                    // setPendingIntentBackgroundActivityLaunchAllowed which is T+ but deprecated.
+                    // Use setPendingIntentBackgroundActivityLaunchAllowed as the U+ version is not
+                    // yet available in all branches.
+                    final ActivityOptions activityOptions = ActivityOptions.makeBasic();
+                    activityOptions.setPendingIntentBackgroundActivityLaunchAllowed(true);
+                    options = activityOptions.toBundle();
+                }
+            }
+
+            intent.send(null, 0, null, null, null, null, options);
         } catch (PendingIntent.CanceledException e) {
             Log.e(TAG, "Error sending dialog PendingIntent", e);
         }
diff --git a/service/src/com/android/server/connectivity/NetworkPreferenceList.java b/service/src/com/android/server/connectivity/NetworkPreferenceList.java
new file mode 100644
index 0000000..fa6d157
--- /dev/null
+++ b/service/src/com/android/server/connectivity/NetworkPreferenceList.java
@@ -0,0 +1,100 @@
+/*
+ * 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 com.android.server.connectivity;
+
+import android.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A generic data class containing network preferences.
+ * @param <K> The type of key in T
+ * @param <T> The type of preference stored in this preference list
+ */
+public class NetworkPreferenceList<K, T extends NetworkPreferenceList.NetworkPreference<K>>
+        implements Iterable<T> {
+    /**
+     * A network preference
+     * @param <K> the type of key by which this preference is indexed. A NetworkPreferenceList
+     *            can have multiple preferences associated with this key, but has methods to
+     *            work on the keys.
+     */
+    public interface NetworkPreference<K> {
+        /**
+         * Whether this preference codes for cancelling the preference for this key
+         *
+         * A preference that codes for cancelling is removed from the list of preferences, since
+         * it means the behavior should be the same as if there was no preference for this key.
+         */
+        boolean isCancel();
+        /** The key */
+        K getKey();
+    }
+
+    @NonNull private final List<T> mPreferences;
+
+    public NetworkPreferenceList() {
+        mPreferences = Collections.EMPTY_LIST;
+    }
+
+    private NetworkPreferenceList(@NonNull final List<T> list) {
+        mPreferences = Collections.unmodifiableList(list);
+    }
+
+    /**
+     * Returns a new object consisting of this object plus the passed preference.
+     *
+     * If the passed preference is a cancel preference (see {@link NetworkPreference#isCancel()}
+     * then it is not added.
+     */
+    public NetworkPreferenceList<K, T> plus(@NonNull final T pref) {
+        final ArrayList<T> newPrefs = new ArrayList<>(mPreferences);
+        if (!pref.isCancel()) {
+            newPrefs.add(pref);
+        }
+        return new NetworkPreferenceList<>(newPrefs);
+    }
+
+    /**
+     * Remove all preferences corresponding to a key.
+     */
+    public NetworkPreferenceList<K, T> minus(@NonNull final K key) {
+        final ArrayList<T> newPrefs = new ArrayList<>();
+        for (final T existingPref : mPreferences) {
+            if (!existingPref.getKey().equals(key)) {
+                newPrefs.add(existingPref);
+            }
+        }
+        return new NetworkPreferenceList<>(newPrefs);
+    }
+
+    public boolean isEmpty() {
+        return mPreferences.isEmpty();
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+        return mPreferences.iterator();
+    }
+
+    @Override public String toString() {
+        return "NetworkPreferenceList : " + mPreferences;
+    }
+}
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index ff979d8..c15f042 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -1211,18 +1211,6 @@
         }
     }
 
-    /** Should only be used by unit tests */
-    @VisibleForTesting
-    public synchronized Set<UidRange> getVpnInterfaceUidRanges(String iface) {
-        return mVpnInterfaceUidRanges.get(iface);
-    }
-
-    /** Should only be used by unit tests */
-    @VisibleForTesting
-    synchronized Set<UidRange> getVpnLockdownUidRanges() {
-        return mVpnLockdownUidRanges.getSet();
-    }
-
     private synchronized void onSettingChanged() {
         // Step1. Update uids allowed to use restricted networks and compute the set of uids to
         // update.
diff --git a/service/src/com/android/server/connectivity/ProfileNetworkPreferenceInfo.java b/service/src/com/android/server/connectivity/ProfileNetworkPreferenceInfo.java
new file mode 100644
index 0000000..7679660
--- /dev/null
+++ b/service/src/com/android/server/connectivity/ProfileNetworkPreferenceInfo.java
@@ -0,0 +1,65 @@
+/*
+ * 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 com.android.server.connectivity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.NetworkCapabilities;
+import android.os.UserHandle;
+
+/**
+ * A single profile preference, as it applies to a given user profile.
+ */
+public class ProfileNetworkPreferenceInfo
+        implements NetworkPreferenceList.NetworkPreference<UserHandle> {
+    @NonNull
+    public final UserHandle user;
+    // Capabilities are only null when sending an object to remove the setting for a user
+    @Nullable
+    public final NetworkCapabilities capabilities;
+    public final boolean allowFallback;
+    public final boolean blockingNonEnterprise;
+
+    public ProfileNetworkPreferenceInfo(@NonNull final UserHandle user,
+            @Nullable final NetworkCapabilities capabilities,
+            final boolean allowFallback, final boolean blockingNonEnterprise) {
+        this.user = user;
+        this.capabilities = null == capabilities ? null : new NetworkCapabilities(capabilities);
+        this.allowFallback = allowFallback;
+        this.blockingNonEnterprise = blockingNonEnterprise;
+    }
+
+    @Override
+    public boolean isCancel() {
+        return null == capabilities;
+    }
+
+    @Override
+    @NonNull
+    public UserHandle getKey() {
+        return user;
+    }
+
+    /** toString */
+    public String toString() {
+        return "[ProfileNetworkPreference user=" + user
+                + " caps=" + capabilities
+                + " allowFallback=" + allowFallback
+                + " blockingNonEnterprise=" + blockingNonEnterprise
+                + "]";
+    }
+}
diff --git a/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java b/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java
deleted file mode 100644
index 5bafef9..0000000
--- a/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.connectivity;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.NetworkCapabilities;
-import android.os.UserHandle;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A data class containing all the per-profile network preferences.
- *
- * A given profile can only have one preference.
- */
-public class ProfileNetworkPreferenceList {
-    /**
-     * A single preference, as it applies to a given user profile.
-     */
-    public static class Preference {
-        @NonNull public final UserHandle user;
-        // Capabilities are only null when sending an object to remove the setting for a user
-        @Nullable public final NetworkCapabilities capabilities;
-        public final boolean allowFallback;
-
-        public Preference(@NonNull final UserHandle user,
-                @Nullable final NetworkCapabilities capabilities,
-                final boolean allowFallback) {
-            this.user = user;
-            this.capabilities = null == capabilities ? null : new NetworkCapabilities(capabilities);
-            this.allowFallback = allowFallback;
-        }
-
-        /** toString */
-        public String toString() {
-            return "[ProfileNetworkPreference user=" + user
-                    + " caps=" + capabilities
-                    + " allowFallback=" + allowFallback
-                    + "]";
-        }
-    }
-
-    @NonNull public final List<Preference> preferences;
-
-    public ProfileNetworkPreferenceList() {
-        preferences = Collections.EMPTY_LIST;
-    }
-
-    private ProfileNetworkPreferenceList(@NonNull final List<Preference> list) {
-        preferences = Collections.unmodifiableList(list);
-    }
-
-    /**
-     * Returns a new object consisting of this object plus the passed preference.
-     *
-     * It is not expected that unwanted preference already exists for the same user.
-     * All preferences for the user that were previously configured should be cleared before
-     * adding a new preference.
-     * Passing a Preference object containing a null capabilities object is equivalent
-     * to removing the preference for this user.
-     */
-    public ProfileNetworkPreferenceList plus(@NonNull final Preference pref) {
-        final ArrayList<Preference> newPrefs = new ArrayList<>(preferences);
-        if (null != pref.capabilities) {
-            newPrefs.add(pref);
-        }
-        return new ProfileNetworkPreferenceList(newPrefs);
-    }
-
-    /**
-     * Remove all preferences corresponding to a user.
-     */
-    public ProfileNetworkPreferenceList withoutUser(UserHandle user) {
-        final ArrayList<Preference> newPrefs = new ArrayList<>();
-        for (final Preference existingPref : preferences) {
-            if (!existingPref.user.equals(user)) {
-                newPrefs.add(existingPref);
-            }
-        }
-        return new ProfileNetworkPreferenceList(newPrefs);
-    }
-
-    public boolean isEmpty() {
-        return preferences.isEmpty();
-    }
-}
diff --git a/service/src/com/android/server/connectivity/UidRangeUtils.java b/service/src/com/android/server/connectivity/UidRangeUtils.java
index 541340b..f36797d 100644
--- a/service/src/com/android/server/connectivity/UidRangeUtils.java
+++ b/service/src/com/android/server/connectivity/UidRangeUtils.java
@@ -184,4 +184,41 @@
         uidRangeSet.add(new UidRange(start, stop));
         return uidRangeSet;
     }
+
+    private static int compare(UidRange range1, UidRange range2) {
+        return range1.start - range2.start;
+    }
+
+    /**
+     * Sort the given UidRange array.
+     *
+     * @param ranges The array of UidRange which is going to be sorted.
+     * @return Array of UidRange.
+     */
+    public static UidRange[] sortRangesByStartUid(UidRange[] ranges) {
+        final ArrayList uidRanges = new ArrayList(Arrays.asList(ranges));
+        Collections.sort(uidRanges, UidRangeUtils::compare);
+        return (UidRange[]) uidRanges.toArray(new UidRange[0]);
+    }
+
+    /**
+     * Check if the given sorted UidRange array contains overlap or not.
+     *
+     * Note that the sorted UidRange array must be sorted by increasing lower bound. If it's not,
+     * the behavior is undefined.
+     *
+     * @param ranges The sorted UidRange array which is going to be checked if there is an overlap
+     *               or not.
+     * @return A boolean to indicate if the given sorted UidRange array contains overlap or not.
+     */
+    public static boolean sortedRangesContainOverlap(UidRange[] ranges) {
+        final ArrayList uidRanges = new ArrayList(Arrays.asList(ranges));
+        for (int i = 0; i + 1 < uidRanges.size(); i++) {
+            if (((UidRange) uidRanges.get(i + 1)).start <= ((UidRange) uidRanges.get(i)).stop) {
+                return true;
+            }
+        }
+
+        return false;
+    }
 }
diff --git a/service/src/com/android/server/connectivity/VpnNetworkPreferenceInfo.java b/service/src/com/android/server/connectivity/VpnNetworkPreferenceInfo.java
new file mode 100644
index 0000000..3e111ab
--- /dev/null
+++ b/service/src/com/android/server/connectivity/VpnNetworkPreferenceInfo.java
@@ -0,0 +1,63 @@
+/*
+ * 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 com.android.server.connectivity;
+
+import android.annotation.NonNull;
+import android.net.UidRange;
+import android.util.ArraySet;
+
+/**
+ * Record the session and UidRange for a VPN preference.
+ */
+public class VpnNetworkPreferenceInfo
+        implements NetworkPreferenceList.NetworkPreference<String> {
+
+    @NonNull
+    public final String mSession;
+
+    @NonNull
+    public final ArraySet<UidRange> mUidRanges;
+
+    public VpnNetworkPreferenceInfo(@NonNull String session,
+            @NonNull ArraySet<UidRange> uidRanges) {
+        this.mSession = session;
+        this.mUidRanges = uidRanges;
+    }
+
+    @Override
+    public boolean isCancel() {
+        return mUidRanges.isEmpty();
+    }
+
+    @Override
+    @NonNull
+    public String getKey() {
+        return mSession;
+    }
+
+    @NonNull
+    public ArraySet<UidRange> getUidRangesNoCopy() {
+        return mUidRanges;
+    }
+
+    /** toString */
+    public String toString() {
+        return "[VpnNetworkPreference session = " + mSession
+                + " uidRanges = " + mUidRanges
+                + "]";
+    }
+}
diff --git a/service/src/com/android/server/connectivity/wear/CompanionDeviceManagerProxyService.java b/service/src/com/android/server/connectivity/wear/CompanionDeviceManagerProxyService.java
new file mode 100644
index 0000000..d3e3843
--- /dev/null
+++ b/service/src/com/android/server/connectivity/wear/CompanionDeviceManagerProxyService.java
@@ -0,0 +1,60 @@
+/*
+ * 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 com.android.server.connectivity.wear;
+
+import android.annotation.SuppressLint;
+import android.companion.AssociationInfo;
+import android.companion.CompanionDeviceManager;
+import android.content.Context;
+import android.net.wear.ICompanionDeviceManagerProxy;
+import android.os.Binder;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.net.module.util.PermissionUtils;
+
+import java.util.List;
+
+/**
+ * A proxy for {@link CompanionDeviceManager}, for use by Tethering with NetworkStack permissions.
+ */
+public class CompanionDeviceManagerProxyService extends ICompanionDeviceManagerProxy.Stub {
+    private final Context mContext;
+
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    public CompanionDeviceManagerProxyService(Context context) {
+        mContext = context;
+    }
+
+    // TODO(b/193460475): Android Lint handles change from SystemApi to public incorrectly.
+    // CompanionDeviceManager#getAllAssociations() is made public in U,
+    // but existed in T as an identical SystemApi.
+    @SuppressLint("NewApi")
+    @Override
+    public List<AssociationInfo> getAllAssociations() {
+        PermissionUtils.enforceNetworkStackPermission(mContext);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            final CompanionDeviceManager cdm = mContext.getSystemService(
+                    CompanionDeviceManager.class);
+            return cdm.getAllAssociations();
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+}
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index 5c9cc63..8e47235 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -65,7 +65,7 @@
     defaults: ["jarjar-rules-combine-defaults"],
     srcs: [
         "tethering-jni-jarjar-rules.txt",
-        ":connectivity-jarjar-rules",
+        ":frameworks-net-tests-jarjar-rules",
         ":TetheringTestsJarJarRules",
         ":NetworkStackJarJarRules",
     ],
diff --git a/tests/common/AndroidTest_Coverage.xml b/tests/common/AndroidTest_Coverage.xml
index 48d26b8..c94ec27 100644
--- a/tests/common/AndroidTest_Coverage.xml
+++ b/tests/common/AndroidTest_Coverage.xml
@@ -13,7 +13,7 @@
      limitations under the License.
 -->
 <configuration description="Runs coverage tests for Connectivity">
-    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
       <option name="test-file-name" value="ConnectivityCoverageTests.apk" />
       <option name="install-arg" value="-t" />
     </target_preparer>
diff --git a/tests/common/java/android/net/LinkPropertiesTest.java b/tests/common/java/android/net/LinkPropertiesTest.java
index 5ee375f..09f5d6e 100644
--- a/tests/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/common/java/android/net/LinkPropertiesTest.java
@@ -32,6 +32,7 @@
 
 import android.compat.testing.PlatformCompatChangeRule;
 import android.net.LinkProperties.ProvisioningChange;
+import android.net.connectivity.ConnectivityCompatChanges;
 import android.os.Build;
 import android.system.OsConstants;
 import android.util.ArraySet;
@@ -1261,7 +1262,7 @@
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.R)
     @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
-    @EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
+    @EnableCompatChanges({ConnectivityCompatChanges.EXCLUDED_ROUTES})
     public void testHasExcludeRoute() {
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName("tun0");
@@ -1274,7 +1275,7 @@
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.R)
     @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
-    @EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
+    @EnableCompatChanges({ConnectivityCompatChanges.EXCLUDED_ROUTES})
     public void testRouteAddWithSameKey() throws Exception {
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName("wlan0");
@@ -1348,14 +1349,14 @@
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.R)
     @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
-    @EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
+    @EnableCompatChanges({ConnectivityCompatChanges.EXCLUDED_ROUTES})
     public void testExcludedRoutesEnabledByCompatChange() {
         assertExcludeRoutesVisible();
     }
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.R)
     @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
-    @DisableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
+    @DisableCompatChanges({ConnectivityCompatChanges.EXCLUDED_ROUTES})
     public void testExcludedRoutesDisabledByCompatChange() {
         checkExcludeRoutesNotVisibleAfterS();
     }
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index 7b374d2..aae3425 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -21,6 +21,7 @@
 import static android.net.NetworkCapabilities.MIN_TRANSPORT;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
@@ -36,6 +37,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
@@ -113,6 +115,9 @@
     private static final int TEST_SUBID2 = 2;
     private static final int TEST_SUBID3 = 3;
 
+    private static final Set<Integer> TEST_NETWORKS_EXTRA_ALLOWED_CAPS_ON_NON_CELL =
+            Set.of(NET_CAPABILITY_CBS, NET_CAPABILITY_DUN, NET_CAPABILITY_RCS);
+
     @Rule
     public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule();
 
@@ -1321,16 +1326,31 @@
     }
 
     @Test
-    public void testRestrictCapabilitiesForTestNetworkByNotOwnerWithRestrictedNc() {
-        testRestrictCapabilitiesForTestNetworkWithRestrictedNc(false /* isOwner */);
+    public void testRestrictCapabilitiesForTestNetworkRestrictedNc_NotOwner_NotCell() {
+        testRestrictCapabilitiesForTestNetworkWithRestrictedNc(
+                false /* isOwner */, false /* isCell */);
     }
 
     @Test
-    public void testRestrictCapabilitiesForTestNetworkByOwnerWithRestrictedNc() {
-        testRestrictCapabilitiesForTestNetworkWithRestrictedNc(true /* isOwner */);
+    public void testRestrictCapabilitiesForTestNetworkRestrictedNc_Owner_NotCell() {
+        testRestrictCapabilitiesForTestNetworkWithRestrictedNc(
+                true /* isOwner */, false /* isCell */);
     }
 
-    private void testRestrictCapabilitiesForTestNetworkWithRestrictedNc(boolean isOwner) {
+    @Test
+    public void testRestrictCapabilitiesForTestNetworkRestrictedNc_NotOwner_Cell() {
+        testRestrictCapabilitiesForTestNetworkWithRestrictedNc(
+                false /* isOwner */, true /* isCell */);
+    }
+
+    @Test
+    public void testRestrictCapabilitiesForTestNetworkRestrictedNc_Owner_Cell() {
+        testRestrictCapabilitiesForTestNetworkWithRestrictedNc(
+                true /* isOwner */, false /* isCell */);
+    }
+
+    private void testRestrictCapabilitiesForTestNetworkWithRestrictedNc(
+            boolean isOwner, boolean isCell) {
         final int ownerUid = 1234;
         final int signalStrength = -80;
         final int[] administratorUids = {1001, ownerUid};
@@ -1339,29 +1359,50 @@
         // the networkCapabilities will contain more than one transport type. However,
         // networkCapabilities must have a single transport specified to use NetworkSpecifier. Thus,
         // do not verify this part since it's verified in other tests.
-        final NetworkCapabilities restrictedNc = new NetworkCapabilities.Builder()
+        final NetworkCapabilities.Builder restrictedNcBuilder = new NetworkCapabilities.Builder()
                 .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
-                .addTransportType(TRANSPORT_CELLULAR)
                 .addCapability(NET_CAPABILITY_MMS)
                 .addCapability(NET_CAPABILITY_NOT_METERED)
                 .setAdministratorUids(administratorUids)
                 .setOwnerUid(ownerUid)
                 .setSignalStrength(signalStrength)
                 .setTransportInfo(transportInfo)
-                .setSubscriptionIds(Set.of(TEST_SUBID1)).build();
+                .setSubscriptionIds(Set.of(TEST_SUBID1));
+        for (int cap : TEST_NETWORKS_EXTRA_ALLOWED_CAPS_ON_NON_CELL) {
+            restrictedNcBuilder.addCapability(cap);
+        }
+
+        if (isCell) {
+            restrictedNcBuilder.addTransportType(TRANSPORT_CELLULAR);
+        }
+        final NetworkCapabilities restrictedNc = restrictedNcBuilder.build();
+
         final int creatorUid = isOwner ? ownerUid : INVALID_UID;
         restrictedNc.restrictCapabilitiesForTestNetwork(creatorUid);
 
         final NetworkCapabilities.Builder expectedNcBuilder = new NetworkCapabilities.Builder()
                 .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
-        // If the test network is restricted, then the network may declare any transport, and
-        // appended with TRANSPORT_TEST.
-        expectedNcBuilder.addTransportType(TRANSPORT_CELLULAR);
+
+        if (isCell) {
+            // If the test network is restricted, then the network may declare any transport, and
+            // appended with TRANSPORT_TEST.
+            expectedNcBuilder.addTransportType(TRANSPORT_CELLULAR);
+        } else {
+            // If the test network only has TRANSPORT_TEST, then it can keep the subscription IDs.
+            expectedNcBuilder.setSubscriptionIds(Set.of(TEST_SUBID1));
+        }
         expectedNcBuilder.addTransportType(TRANSPORT_TEST);
+
         // Only TEST_NETWORKS_ALLOWED_CAPABILITIES will be kept.
         expectedNcBuilder.addCapability(NET_CAPABILITY_NOT_METERED);
         expectedNcBuilder.removeCapability(NET_CAPABILITY_TRUSTED);
 
+        if (!isCell) {
+            for (int cap : TEST_NETWORKS_EXTRA_ALLOWED_CAPS_ON_NON_CELL) {
+                expectedNcBuilder.addCapability(cap);
+            }
+        }
+
         expectedNcBuilder.setSignalStrength(signalStrength).setTransportInfo(transportInfo);
         if (creatorUid == ownerUid) {
             // Only retain the owner and administrator UIDs if they match the app registering the
diff --git a/tests/common/java/android/net/NetworkProviderTest.kt b/tests/common/java/android/net/NetworkProviderTest.kt
index c0e7f61..fcbb0dd 100644
--- a/tests/common/java/android/net/NetworkProviderTest.kt
+++ b/tests/common/java/android/net/NetworkProviderTest.kt
@@ -79,6 +79,7 @@
     @After
     fun tearDown() {
         mHandlerThread.quitSafely()
+        mHandlerThread.join()
         instrumentation.getUiAutomation().dropShellPermissionIdentity()
     }
 
diff --git a/tests/common/java/android/net/VpnTransportInfoTest.java b/tests/common/java/android/net/VpnTransportInfoTest.java
index 161f9ee..2d01df7 100644
--- a/tests/common/java/android/net/VpnTransportInfoTest.java
+++ b/tests/common/java/android/net/VpnTransportInfoTest.java
@@ -53,7 +53,7 @@
         assertParcelingIsLossless(v);
 
         final VpnTransportInfo v2 =
-                new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, "12345", true);
+                new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, "12345", true, true);
         assertParcelingIsLossless(v2);
     }
 
@@ -66,8 +66,10 @@
         final VpnTransportInfo v13 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, session1);
         final VpnTransportInfo v14 = new VpnTransportInfo(VpnManager.TYPE_VPN_LEGACY, session1);
         final VpnTransportInfo v15 = new VpnTransportInfo(VpnManager.TYPE_VPN_OEM, session1);
-        final VpnTransportInfo v16 = new VpnTransportInfo(VpnManager.TYPE_VPN_OEM, session1, true);
-        final VpnTransportInfo v17 = new VpnTransportInfo(VpnManager.TYPE_VPN_OEM, session1, true);
+        final VpnTransportInfo v16 = new VpnTransportInfo(
+                VpnManager.TYPE_VPN_OEM, session1, true, true);
+        final VpnTransportInfo v17 = new VpnTransportInfo(
+                VpnManager.TYPE_VPN_OEM, session1, true, true);
         final VpnTransportInfo v21 = new VpnTransportInfo(VpnManager.TYPE_VPN_LEGACY, session2);
 
         final VpnTransportInfo v31 = v11.makeCopy(REDACT_FOR_NETWORK_SETTINGS);
@@ -92,19 +94,30 @@
 
     @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.TIRAMISU)
     @Test
-    public void testGetBypassable_beforeU() {
+    public void testIsBypassable_beforeU() {
         final VpnTransportInfo v = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, "12345");
-        assertThrows(UnsupportedOperationException.class, () -> v.getBypassable());
+        assertThrows(UnsupportedOperationException.class, () -> v.isBypassable());
     }
 
     @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
     @Test
-    public void testGetBypassable_afterU() {
+    public void testIsBypassable_afterU() {
         final VpnTransportInfo v = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, "12345");
-        assertFalse(v.getBypassable());
+        assertFalse(v.isBypassable());
 
         final VpnTransportInfo v2 =
-                new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, "12345", true);
-        assertTrue(v2.getBypassable());
+                new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, "12345", true, false);
+        assertTrue(v2.isBypassable());
+    }
+
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testShouldLongLivedTcpExcluded() {
+        final VpnTransportInfo v = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, "12345");
+        assertFalse(v.areLongLivedTcpConnectionsExpensive());
+
+        final VpnTransportInfo v2 = new VpnTransportInfo(
+                VpnManager.TYPE_VPN_PLATFORM, "12345", true, true);
+        assertTrue(v2.areLongLivedTcpConnectionsExpensive());
     }
 }
diff --git a/tests/common/java/android/net/netstats/NetworkTemplateTest.kt b/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
index cdf32a4..fd7bd74 100644
--- a/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
+++ b/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
@@ -19,31 +19,28 @@
 import android.net.NetworkStats.DEFAULT_NETWORK_ALL
 import android.net.NetworkStats.METERED_ALL
 import android.net.NetworkStats.METERED_YES
-import android.net.NetworkStats.ROAMING_YES
 import android.net.NetworkStats.ROAMING_ALL
+import android.net.NetworkStats.ROAMING_YES
 import android.net.NetworkTemplate
 import android.net.NetworkTemplate.MATCH_BLUETOOTH
 import android.net.NetworkTemplate.MATCH_CARRIER
 import android.net.NetworkTemplate.MATCH_ETHERNET
 import android.net.NetworkTemplate.MATCH_MOBILE
-import android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD
 import android.net.NetworkTemplate.MATCH_PROXY
 import android.net.NetworkTemplate.MATCH_WIFI
-import android.net.NetworkTemplate.MATCH_WIFI_WILDCARD
 import android.net.NetworkTemplate.NETWORK_TYPE_ALL
 import android.net.NetworkTemplate.OEM_MANAGED_ALL
+import android.os.Build
 import android.telephony.TelephonyManager
-import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL
-import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT
 import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.SC_V2
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
 
 private const val TEST_IMSI1 = "imsi"
 private const val TEST_WIFI_KEY1 = "wifiKey1"
@@ -66,10 +63,8 @@
         }
 
         // Verify hidden match rules cannot construct templates.
-        listOf(MATCH_WIFI_WILDCARD, MATCH_MOBILE_WILDCARD, MATCH_PROXY).forEach {
-            assertFailsWith<IllegalArgumentException> {
-                NetworkTemplate.Builder(it).build()
-            }
+        assertFailsWith<IllegalArgumentException> {
+            NetworkTemplate.Builder(MATCH_PROXY).build()
         }
 
         // Verify template which matches metered cellular and carrier networks with
@@ -77,10 +72,9 @@
         listOf(MATCH_MOBILE, MATCH_CARRIER).forEach { matchRule ->
             NetworkTemplate.Builder(matchRule).setSubscriberIds(setOf(TEST_IMSI1))
                     .setMeteredness(METERED_YES).build().let {
-                        val expectedTemplate = NetworkTemplate(matchRule, TEST_IMSI1,
-                                arrayOf(TEST_IMSI1), emptyArray<String>(), METERED_YES,
-                                ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
-                                OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+                        val expectedTemplate = NetworkTemplate(matchRule, arrayOf(TEST_IMSI1),
+                                emptyArray<String>(), METERED_YES, ROAMING_ALL, DEFAULT_NETWORK_ALL,
+                                NETWORK_TYPE_ALL, OEM_MANAGED_ALL)
                         assertEquals(expectedTemplate, it)
                     }
         }
@@ -90,10 +84,9 @@
         listOf(MATCH_MOBILE, MATCH_CARRIER).forEach { matchRule ->
             NetworkTemplate.Builder(matchRule).setSubscriberIds(setOf(TEST_IMSI1))
                     .setRoaming(ROAMING_YES).setMeteredness(METERED_YES).build().let {
-                        val expectedTemplate = NetworkTemplate(matchRule, TEST_IMSI1,
-                                arrayOf(TEST_IMSI1), emptyArray<String>(), METERED_YES,
-                                ROAMING_YES, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
-                                OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+                        val expectedTemplate = NetworkTemplate(matchRule, arrayOf(TEST_IMSI1),
+                                emptyArray<String>(), METERED_YES, ROAMING_YES, DEFAULT_NETWORK_ALL,
+                                NETWORK_TYPE_ALL, OEM_MANAGED_ALL)
                         assertEquals(expectedTemplate, it)
                     }
         }
@@ -103,45 +96,67 @@
             NetworkTemplate.Builder(MATCH_CARRIER).build()
         }
 
+        // Verify carrier and mobile template cannot contain one of subscriber Id is null.
+        assertFailsWith<IllegalArgumentException> {
+            NetworkTemplate.Builder(MATCH_CARRIER).setSubscriberIds(setOf(null)).build()
+        }
+        val firstSdk = Build.VERSION.DEVICE_INITIAL_SDK_INT
+        if (firstSdk > Build.VERSION_CODES.TIRAMISU) {
+            assertFailsWith<IllegalArgumentException> {
+                NetworkTemplate.Builder(MATCH_MOBILE).setSubscriberIds(setOf(null)).build()
+            }
+        } else {
+            NetworkTemplate.Builder(MATCH_MOBILE).setSubscriberIds(setOf(null)).build().let {
+                val expectedTemplate = NetworkTemplate(
+                    MATCH_MOBILE,
+                    arrayOfNulls<String>(1) /*subscriberIds*/,
+                    emptyArray<String>() /*wifiNetworkKey*/,
+                    METERED_ALL,
+                    ROAMING_ALL,
+                    DEFAULT_NETWORK_ALL,
+                    NETWORK_TYPE_ALL,
+                    OEM_MANAGED_ALL
+                )
+                assertEquals(expectedTemplate, it)
+            }
+        }
+
         // Verify template which matches metered cellular networks,
         // regardless of IMSI. See buildTemplateMobileWildcard.
         NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES).build().let {
-            val expectedTemplate = NetworkTemplate(MATCH_MOBILE_WILDCARD, null /*subscriberId*/,
-                    emptyArray<String>() /*subscriberIds*/, emptyArray<String>(),
+            val expectedTemplate = NetworkTemplate(MATCH_MOBILE,
+                    emptyArray<String>() /*subscriberIds*/, emptyArray<String>() /*wifiNetworkKey*/,
                     METERED_YES, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
-                    OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+                    OEM_MANAGED_ALL)
             assertEquals(expectedTemplate, it)
         }
 
         // Verify template which matches metered cellular networks and ratType.
-        // See NetworkTemplate#buildTemplateMobileWithRatType.
         NetworkTemplate.Builder(MATCH_MOBILE).setSubscriberIds(setOf(TEST_IMSI1))
                 .setMeteredness(METERED_YES).setRatType(TelephonyManager.NETWORK_TYPE_UMTS)
                 .build().let {
-                    val expectedTemplate = NetworkTemplate(MATCH_MOBILE, TEST_IMSI1,
-                            arrayOf(TEST_IMSI1), emptyArray<String>(), METERED_YES,
-                            ROAMING_ALL, DEFAULT_NETWORK_ALL, TelephonyManager.NETWORK_TYPE_UMTS,
-                            OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+                    val expectedTemplate = NetworkTemplate(MATCH_MOBILE, arrayOf(TEST_IMSI1),
+                            emptyArray<String>(), METERED_YES, ROAMING_ALL, DEFAULT_NETWORK_ALL,
+                            TelephonyManager.NETWORK_TYPE_UMTS, OEM_MANAGED_ALL)
                     assertEquals(expectedTemplate, it)
                 }
 
         // Verify template which matches all wifi networks,
         // regardless of Wifi Network Key. See buildTemplateWifiWildcard and buildTemplateWifi.
         NetworkTemplate.Builder(MATCH_WIFI).build().let {
-            val expectedTemplate = NetworkTemplate(MATCH_WIFI_WILDCARD, null /*subscriberId*/,
-                    emptyArray<String>() /*subscriberIds*/, emptyArray<String>(),
-                    METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
-                    OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+            val expectedTemplate = NetworkTemplate(MATCH_WIFI,
+                    emptyArray<String>() /*subscriberIds*/, emptyArray<String>(), METERED_ALL,
+                    ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL)
             assertEquals(expectedTemplate, it)
         }
 
         // Verify template which matches wifi networks with the given Wifi Network Key.
         // See buildTemplateWifi(wifiNetworkKey).
         NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build().let {
-            val expectedTemplate = NetworkTemplate(MATCH_WIFI, null /*subscriberId*/,
-                    emptyArray<String>() /*subscriberIds*/, arrayOf(TEST_WIFI_KEY1),
-                    METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
-                    OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+            val expectedTemplate =
+                    NetworkTemplate(MATCH_WIFI, emptyArray<String>() /*subscriberIds*/,
+                    arrayOf(TEST_WIFI_KEY1), METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL,
+                    NETWORK_TYPE_ALL, OEM_MANAGED_ALL)
             assertEquals(expectedTemplate, it)
         }
 
@@ -149,10 +164,9 @@
         // given Wifi Network Key, and IMSI. See buildTemplateWifi(wifiNetworkKey, subscriberId).
         NetworkTemplate.Builder(MATCH_WIFI).setSubscriberIds(setOf(TEST_IMSI1))
                 .setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build().let {
-                    val expectedTemplate = NetworkTemplate(MATCH_WIFI, TEST_IMSI1,
-                            arrayOf(TEST_IMSI1), arrayOf(TEST_WIFI_KEY1),
-                            METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
-                            OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+                    val expectedTemplate = NetworkTemplate(MATCH_WIFI, arrayOf(TEST_IMSI1),
+                            arrayOf(TEST_WIFI_KEY1), METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL,
+                            NETWORK_TYPE_ALL, OEM_MANAGED_ALL)
                     assertEquals(expectedTemplate, it)
                 }
 
@@ -160,10 +174,10 @@
         // See buildTemplateEthernet and buildTemplateBluetooth.
         listOf(MATCH_ETHERNET, MATCH_BLUETOOTH).forEach { matchRule ->
             NetworkTemplate.Builder(matchRule).build().let {
-                val expectedTemplate = NetworkTemplate(matchRule, null /*subscriberId*/,
+                val expectedTemplate = NetworkTemplate(matchRule,
                         emptyArray<String>() /*subscriberIds*/, emptyArray<String>(),
                         METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
-                        OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+                        OEM_MANAGED_ALL)
                 assertEquals(expectedTemplate, it)
             }
         }
@@ -195,10 +209,10 @@
 
         // Verify template which matches wifi wildcard with the given empty key set.
         NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf<String>()).build().let {
-            val expectedTemplate = NetworkTemplate(MATCH_WIFI_WILDCARD, null /*subscriberId*/,
+            val expectedTemplate = NetworkTemplate(MATCH_WIFI,
                     emptyArray<String>() /*subscriberIds*/, emptyArray<String>(),
                     METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
-                    OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+                    OEM_MANAGED_ALL)
             assertEquals(expectedTemplate, it)
         }
     }
diff --git a/tests/cts/OWNERS b/tests/cts/OWNERS
index 089d06f..8388cb7 100644
--- a/tests/cts/OWNERS
+++ b/tests/cts/OWNERS
@@ -2,6 +2,5 @@
 set noparent
 file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking_xts
 
-# Only temporary ownership to improve ethernet code quality (b/236280707)
-# TODO: remove by 12/31/2022
-per-file net/src/android/net/cts/EthernetManagerTest.kt = prohr@google.com #{LAST_RESORT_SUGGESTION}
+# IPsec
+per-file **IpSec* = benedictwong@google.com, nharold@google.com
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index 47ea53e..891c2dd 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -12,6 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+next_app_data = [ ":CtsHostsideNetworkTestsAppNext" ]
+
+// The above line is put in place to prevent any future automerger merge conflict between aosp,
+// downstream branches. The CtsHostsideNetworkTestsAppNext target will not exist in
+// some downstream branches, but it should exist in aosp and some downstream branches.
+
+
+
+
+
 package {
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
@@ -37,7 +47,9 @@
     data: [
         ":CtsHostsideNetworkTestsApp",
         ":CtsHostsideNetworkTestsApp2",
-        ":CtsHostsideNetworkTestsAppNext",
-    ],
+        ":CtsHostsideNetworkCapTestsAppWithoutProperty",
+        ":CtsHostsideNetworkCapTestsAppWithProperty",
+        ":CtsHostsideNetworkCapTestsAppSdk33",
+    ] + next_app_data,
     per_testcase_directory: true,
 }
diff --git a/tests/cts/hostside/AndroidTest.xml b/tests/cts/hostside/AndroidTest.xml
index 7a73313..e83e36a 100644
--- a/tests/cts/hostside/AndroidTest.xml
+++ b/tests/cts/hostside/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for CTS net 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" />
diff --git a/tests/cts/hostside/TEST_MAPPING b/tests/cts/hostside/TEST_MAPPING
index ab6de82..2cfd7af 100644
--- a/tests/cts/hostside/TEST_MAPPING
+++ b/tests/cts/hostside/TEST_MAPPING
@@ -8,6 +8,9 @@
         },
         {
           "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        },
+        {
+          "exclude-annotation": "android.platform.test.annotations.RequiresDevice"
         }
       ]
     }
diff --git a/tests/cts/hostside/app/Android.bp b/tests/cts/hostside/app/Android.bp
index 12e7d33..2245382 100644
--- a/tests/cts/hostside/app/Android.bp
+++ b/tests/cts/hostside/app/Android.bp
@@ -30,7 +30,6 @@
         "cts-net-utils",
         "ctstestrunner-axt",
         "modules-utils-build",
-        "ub-uiautomator",
     ],
     libs: [
         "android.test.runner",
diff --git a/tests/cts/hostside/app/AndroidManifest.xml b/tests/cts/hostside/app/AndroidManifest.xml
index 56d3cb5..ca3397b 100644
--- a/tests/cts/hostside/app/AndroidManifest.xml
+++ b/tests/cts/hostside/app/AndroidManifest.xml
@@ -34,7 +34,8 @@
 
     <application android:requestLegacyExternalStorage="true">
         <uses-library android:name="android.test.runner"/>
-        <activity android:name=".MyActivity"/>
+        <activity android:name=".MyActivity"
+                  android:configChanges="density|fontScale|keyboard|keyboardHidden|layoutDirection|locale|mcc|mnc|navigation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode"/>
         <service android:name=".MyVpnService"
              android:permission="android.permission.BIND_VPN_SERVICE"
              android:exported="true">
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataWarningReceiverTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataWarningReceiverTest.java
index b2e81ff..13bbab6 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataWarningReceiverTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataWarningReceiverTest.java
@@ -19,18 +19,18 @@
 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.clearSnoozeTimestamps;
 
 import android.content.pm.PackageManager;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.Direction;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionPlan;
 
 import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
 
 import com.android.compatibility.common.util.SystemUtil;
-import com.android.compatibility.common.util.UiAutomatorUtils;
+import com.android.compatibility.common.util.UiAutomatorUtils2;
 
 import org.junit.After;
 import org.junit.Assume;
@@ -84,7 +84,7 @@
             final UiDevice uiDevice = UiDevice.getInstance(mInstrumentation);
             uiDevice.openNotification();
             try {
-                final UiObject2 uiObject = UiAutomatorUtils.waitFindObject(
+                final UiObject2 uiObject = UiAutomatorUtils2.waitFindObject(
                         By.text("Data warning"));
                 Assume.assumeNotNull(uiObject);
                 uiObject.wait(Until.clickable(true), 10_000L);
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
index 449454e..fe522a0 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
@@ -32,6 +32,7 @@
 import com.android.networkstack.apishim.VpnServiceBuilderShimImpl;
 import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
 import com.android.networkstack.apishim.common.VpnServiceBuilderShim;
+import com.android.testutils.PacketReflector;
 
 import java.io.IOException;
 import java.net.InetAddress;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/PacketReflector.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/PacketReflector.java
deleted file mode 100644
index 124c2c3..0000000
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/PacketReflector.java
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.net.hostside;
-
-import static android.system.OsConstants.ICMP6_ECHO_REPLY;
-import static android.system.OsConstants.ICMP6_ECHO_REQUEST;
-import static android.system.OsConstants.ICMP_ECHO;
-import static android.system.OsConstants.ICMP_ECHOREPLY;
-
-import android.system.ErrnoException;
-import android.system.Os;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-
-public class PacketReflector extends Thread {
-
-    private static int IPV4_HEADER_LENGTH = 20;
-    private static int IPV6_HEADER_LENGTH = 40;
-
-    private static int IPV4_ADDR_OFFSET = 12;
-    private static int IPV6_ADDR_OFFSET = 8;
-    private static int IPV4_ADDR_LENGTH = 4;
-    private static int IPV6_ADDR_LENGTH = 16;
-
-    private static int IPV4_PROTO_OFFSET = 9;
-    private static int IPV6_PROTO_OFFSET = 6;
-
-    private static final byte IPPROTO_ICMP = 1;
-    private static final byte IPPROTO_TCP = 6;
-    private static final byte IPPROTO_UDP = 17;
-    private static final byte IPPROTO_ICMPV6 = 58;
-
-    private static int ICMP_HEADER_LENGTH = 8;
-    private static int TCP_HEADER_LENGTH = 20;
-    private static int UDP_HEADER_LENGTH = 8;
-
-    private static final byte ICMP_ECHO = 8;
-    private static final byte ICMP_ECHOREPLY = 0;
-
-    private static String TAG = "PacketReflector";
-
-    private FileDescriptor mFd;
-    private byte[] mBuf;
-
-    public PacketReflector(FileDescriptor fd, int mtu) {
-        super("PacketReflector");
-        mFd = fd;
-        mBuf = new byte[mtu];
-    }
-
-    private static void swapBytes(byte[] buf, int pos1, int pos2, int len) {
-        for (int i = 0; i < len; i++) {
-            byte b = buf[pos1 + i];
-            buf[pos1 + i] = buf[pos2 + i];
-            buf[pos2 + i] = b;
-        }
-    }
-
-    private static void swapAddresses(byte[] buf, int version) {
-        int addrPos, addrLen;
-        switch(version) {
-            case 4:
-                addrPos = IPV4_ADDR_OFFSET;
-                addrLen = IPV4_ADDR_LENGTH;
-                break;
-            case 6:
-                addrPos = IPV6_ADDR_OFFSET;
-                addrLen = IPV6_ADDR_LENGTH;
-                break;
-            default:
-                throw new IllegalArgumentException();
-        }
-        swapBytes(buf, addrPos, addrPos + addrLen, addrLen);
-    }
-
-    // Reflect TCP packets: swap the source and destination addresses, but don't change the ports.
-    // This is used by the test to "connect to itself" through the VPN.
-    private void processTcpPacket(byte[] buf, int version, int len, int hdrLen) {
-        if (len < hdrLen + TCP_HEADER_LENGTH) {
-            return;
-        }
-
-        // Swap src and dst IP addresses.
-        swapAddresses(buf, version);
-
-        // Send the packet back.
-        writePacket(buf, len);
-    }
-
-    // Echo UDP packets: swap source and destination addresses, and source and destination ports.
-    // This is used by the test to check that the bytes it sends are echoed back.
-    private void processUdpPacket(byte[] buf, int version, int len, int hdrLen) {
-        if (len < hdrLen + UDP_HEADER_LENGTH) {
-            return;
-        }
-
-        // Swap src and dst IP addresses.
-        swapAddresses(buf, version);
-
-        // Swap dst and src ports.
-        int portOffset = hdrLen;
-        swapBytes(buf, portOffset, portOffset + 2, 2);
-
-        // Send the packet back.
-        writePacket(buf, len);
-    }
-
-    private void processIcmpPacket(byte[] buf, int version, int len, int hdrLen) {
-        if (len < hdrLen + ICMP_HEADER_LENGTH) {
-            return;
-        }
-
-        byte type = buf[hdrLen];
-        if (!(version == 4 && type == ICMP_ECHO) &&
-            !(version == 6 && type == (byte) ICMP6_ECHO_REQUEST)) {
-            return;
-        }
-
-        // Save the ping packet we received.
-        byte[] request = buf.clone();
-
-        // Swap src and dst IP addresses, and send the packet back.
-        // This effectively pings the device to see if it replies.
-        swapAddresses(buf, version);
-        writePacket(buf, len);
-
-        // The device should have replied, and buf should now contain a ping response.
-        int received = readPacket(buf);
-        if (received != len) {
-            Log.i(TAG, "Reflecting ping did not result in ping response: " +
-                       "read=" + received + " expected=" + len);
-            return;
-        }
-
-        byte replyType = buf[hdrLen];
-        if ((type == ICMP_ECHO && replyType != ICMP_ECHOREPLY)
-                || (type == (byte) ICMP6_ECHO_REQUEST && replyType != (byte) ICMP6_ECHO_REPLY)) {
-            Log.i(TAG, "Received unexpected ICMP reply: original " + type
-                    + ", reply " + replyType);
-            return;
-        }
-
-        // Compare the response we got with the original packet.
-        // The only thing that should have changed are addresses, type and checksum.
-        // Overwrite them with the received bytes and see if the packet is otherwise identical.
-        request[hdrLen] = buf[hdrLen];          // Type
-        request[hdrLen + 2] = buf[hdrLen + 2];  // Checksum byte 1.
-        request[hdrLen + 3] = buf[hdrLen + 3];  // Checksum byte 2.
-
-        // Since Linux kernel 4.2, net.ipv6.auto_flowlabels is set by default, and therefore
-        // the request and reply may have different IPv6 flow label: ignore that as well.
-        if (version == 6) {
-            request[1] = (byte)(request[1] & 0xf0 | buf[1] & 0x0f);
-            request[2] = buf[2];
-            request[3] = buf[3];
-        }
-
-        for (int i = 0; i < len; i++) {
-            if (buf[i] != request[i]) {
-                Log.i(TAG, "Received non-matching packet when expecting ping response.");
-                return;
-            }
-        }
-
-        // Now swap the addresses again and reflect the packet. This sends a ping reply.
-        swapAddresses(buf, version);
-        writePacket(buf, len);
-    }
-
-    private void writePacket(byte[] buf, int len) {
-        try {
-            Os.write(mFd, buf, 0, len);
-        } catch (ErrnoException|IOException e) {
-            Log.e(TAG, "Error writing packet: " + e.getMessage());
-        }
-    }
-
-    private int readPacket(byte[] buf) {
-        int len;
-        try {
-            len = Os.read(mFd, buf, 0, buf.length);
-        } catch (ErrnoException|IOException e) {
-            Log.e(TAG, "Error reading packet: " + e.getMessage());
-            len = -1;
-        }
-        return len;
-    }
-
-    // Reads one packet from our mFd, and possibly writes the packet back.
-    private void processPacket() {
-        int len = readPacket(mBuf);
-        if (len < 1) {
-            return;
-        }
-
-        int version = mBuf[0] >> 4;
-        int addrPos, protoPos, hdrLen, addrLen;
-        if (version == 4) {
-            hdrLen = IPV4_HEADER_LENGTH;
-            protoPos = IPV4_PROTO_OFFSET;
-            addrPos = IPV4_ADDR_OFFSET;
-            addrLen = IPV4_ADDR_LENGTH;
-        } else if (version == 6) {
-            hdrLen = IPV6_HEADER_LENGTH;
-            protoPos = IPV6_PROTO_OFFSET;
-            addrPos = IPV6_ADDR_OFFSET;
-            addrLen = IPV6_ADDR_LENGTH;
-        } else {
-            return;
-        }
-
-        if (len < hdrLen) {
-            return;
-        }
-
-        byte proto = mBuf[protoPos];
-        switch (proto) {
-            case IPPROTO_ICMP:
-            case IPPROTO_ICMPV6:
-                processIcmpPacket(mBuf, version, len, hdrLen);
-                break;
-            case IPPROTO_TCP:
-                processTcpPacket(mBuf, version, len, hdrLen);
-                break;
-            case IPPROTO_UDP:
-                processUdpPacket(mBuf, version, len, hdrLen);
-                break;
-        }
-    }
-
-    public void run() {
-        Log.i(TAG, "PacketReflector starting fd=" + mFd + " valid=" + mFd.valid());
-        while (!interrupted() && mFd.valid()) {
-            processPacket();
-        }
-        Log.i(TAG, "PacketReflector exiting fd=" + mFd + " valid=" + mFd.valid());
-    }
-}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index 8c18a89..73a6502 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -18,6 +18,8 @@
 
 import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
 import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.READ_DEVICE_CONFIG;
+import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
 import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
 import static android.content.pm.PackageManager.FEATURE_WIFI;
 import static android.net.ConnectivityManager.TYPE_VPN;
@@ -36,11 +38,18 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_DATA_RECEIVED;
+import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_ERROR;
+import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_PAUSED;
+import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_RESUMED;
+import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_STARTED;
+import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_STOPPED;
 import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_LOCKDOWN_VPN;
 import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_NONE;
 import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_EXPORTED;
 import static com.android.testutils.Cleanup.testAndCleanup;
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+import static com.android.testutils.RecorderCallback.CallbackEntry.BLOCKED_STATUS_INT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -63,6 +72,7 @@
 import android.database.Cursor;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
+import android.net.IpSecManager;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
@@ -70,6 +80,7 @@
 import android.net.NetworkRequest;
 import android.net.Proxy;
 import android.net.ProxyInfo;
+import android.net.SocketKeepalive;
 import android.net.TestNetworkInterface;
 import android.net.TestNetworkManager;
 import android.net.TransportInfo;
@@ -78,6 +89,7 @@
 import android.net.VpnService;
 import android.net.VpnTransportInfo;
 import android.net.cts.util.CtsNetUtils;
+import android.net.util.KeepaliveUtils;
 import android.net.wifi.WifiManager;
 import android.os.Build;
 import android.os.Handler;
@@ -86,28 +98,32 @@
 import android.os.Process;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiSelector;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
 import android.system.StructPollfd;
-import android.telephony.TelephonyManager;
 import android.test.MoreAsserts;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Range;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiSelector;
 
 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
 import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.ArrayTrackRecord;
+import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.PacketBuilder;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.RecorderCallback;
+import com.android.testutils.RecorderCallback.CallbackEntry;
 import com.android.testutils.TestableNetworkCallback;
 
 import org.junit.After;
@@ -136,8 +152,9 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Random;
+import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -187,6 +204,12 @@
     public static int SOCKET_TIMEOUT_MS = 100;
     public static String TEST_HOST = "connectivitycheck.gstatic.com";
 
+    private static final String AUTOMATIC_ON_OFF_KEEPALIVE_VERSION =
+                "automatic_on_off_keepalive_version";
+    // Enabled since version 1 means it's always enabled because the version is always above 1
+    private static final String AUTOMATIC_ON_OFF_KEEPALIVE_ENABLED = "1";
+    private static final long TEST_TCP_POLLING_TIMER_EXPIRED_PERIOD_MS = 60_000L;
+
     private UiDevice mDevice;
     private MyActivity mActivity;
     private String mPackageName;
@@ -195,16 +218,18 @@
     private RemoteSocketFactoryClient mRemoteSocketFactoryClient;
     private CtsNetUtils mCtsNetUtils;
     private PackageManager mPackageManager;
-    private TelephonyManager mTelephonyManager;
-
+    private Context mTestContext;
+    private Context mTargetContext;
     Network mNetwork;
-    NetworkCallback mCallback;
     final Object mLock = new Object();
     final Object mLockShutdown = new Object();
 
     private String mOldPrivateDnsMode;
     private String mOldPrivateDnsSpecifier;
 
+    // The registered callbacks.
+    private List<NetworkCallback> mRegisteredCallbacks = new ArrayList<>();
+
     @Rule
     public final DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule();
 
@@ -225,37 +250,59 @@
     @Before
     public void setUp() throws Exception {
         mNetwork = null;
-        mCallback = null;
+        mTestContext = getInstrumentation().getContext();
+        mTargetContext = getInstrumentation().getTargetContext();
         storePrivateDnsSetting();
-
         mDevice = UiDevice.getInstance(getInstrumentation());
-        mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(),
-                MyActivity.class);
+        mActivity = launchActivity(mTargetContext.getPackageName(), MyActivity.class);
         mPackageName = mActivity.getPackageName();
         mCM = (ConnectivityManager) mActivity.getSystemService(Context.CONNECTIVITY_SERVICE);
         mWifiManager = (WifiManager) mActivity.getSystemService(Context.WIFI_SERVICE);
         mRemoteSocketFactoryClient = new RemoteSocketFactoryClient(mActivity);
         mRemoteSocketFactoryClient.bind();
         mDevice.waitForIdle();
-        mCtsNetUtils = new CtsNetUtils(getInstrumentation().getContext());
-        mPackageManager = getInstrumentation().getContext().getPackageManager();
-        mTelephonyManager =
-                getInstrumentation().getContext().getSystemService(TelephonyManager.class);
+        mCtsNetUtils = new CtsNetUtils(mTestContext);
+        mPackageManager = mTestContext.getPackageManager();
     }
 
     @After
     public void tearDown() throws Exception {
         restorePrivateDnsSetting();
         mRemoteSocketFactoryClient.unbind();
-        if (mCallback != null) {
-            mCM.unregisterNetworkCallback(mCallback);
-        }
         mCtsNetUtils.tearDown();
         Log.i(TAG, "Stopping VPN");
         stopVpn();
+        unregisterRegisteredCallbacks();
         mActivity.finish();
     }
 
+    private void registerNetworkCallback(NetworkRequest request, NetworkCallback callback) {
+        mCM.registerNetworkCallback(request, callback);
+        mRegisteredCallbacks.add(callback);
+    }
+
+    private void registerDefaultNetworkCallback(NetworkCallback callback) {
+        mCM.registerDefaultNetworkCallback(callback);
+        mRegisteredCallbacks.add(callback);
+    }
+
+    private void registerSystemDefaultNetworkCallback(NetworkCallback callback, Handler h) {
+        mCM.registerSystemDefaultNetworkCallback(callback, h);
+        mRegisteredCallbacks.add(callback);
+    }
+
+    private void registerDefaultNetworkCallbackForUid(int uid, NetworkCallback callback,
+            Handler h) {
+        mCM.registerDefaultNetworkCallbackForUid(uid, callback, h);
+        mRegisteredCallbacks.add(callback);
+    }
+
+    private void unregisterRegisteredCallbacks() {
+        for (NetworkCallback callback: mRegisteredCallbacks) {
+            mCM.unregisterNetworkCallback(callback);
+        }
+    }
+
     private void prepareVpn() throws Exception {
         final int REQUEST_ID = 42;
 
@@ -371,7 +418,7 @@
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build();
-        mCallback = new NetworkCallback() {
+        final NetworkCallback callback = new NetworkCallback() {
             public void onAvailable(Network network) {
                 synchronized (mLock) {
                     Log.i(TAG, "Got available callback for network=" + network);
@@ -380,7 +427,7 @@
                 }
             }
         };
-        mCM.registerNetworkCallback(request, mCallback);  // Unregistered in tearDown.
+        registerNetworkCallback(request, callback);
 
         // Start the service and wait up for TIMEOUT_MS ms for the VPN to come up.
         establishVpn(addresses, routes, excludedRoutes, allowedApplications, disallowedApplications,
@@ -405,7 +452,7 @@
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build();
-        mCallback = new NetworkCallback() {
+        final NetworkCallback callback = new NetworkCallback() {
             public void onLost(Network network) {
                 synchronized (mLockShutdown) {
                     Log.i(TAG, "Got lost callback for network=" + network
@@ -416,7 +463,7 @@
                 }
             }
        };
-        mCM.registerNetworkCallback(request, mCallback);  // Unregistered in tearDown.
+        registerNetworkCallback(request, callback);
         // Simply calling mActivity.stopService() won't stop the service, because the system binds
         // to the service for the purpose of sending it a revoke command if another VPN comes up,
         // and stopping a bound service has no effect. Instead, "start" the service again with an
@@ -739,7 +786,7 @@
     }
 
     private ContentResolver getContentResolver() {
-        return getInstrumentation().getContext().getContentResolver();
+        return mTestContext.getContentResolver();
     }
 
     private boolean isPrivateDnsInStrictMode() {
@@ -761,34 +808,16 @@
                 mOldPrivateDnsSpecifier);
     }
 
-    // TODO: replace with CtsNetUtils.awaitPrivateDnsSetting in Q or above.
     private void expectPrivateDnsHostname(final String hostname) throws Exception {
-        final NetworkRequest request = new NetworkRequest.Builder()
-                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
-                .build();
-        final CountDownLatch latch = new CountDownLatch(1);
-        final NetworkCallback callback = new NetworkCallback() {
-            @Override
-            public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
-                if (network.equals(mNetwork) &&
-                        Objects.equals(lp.getPrivateDnsServerName(), hostname)) {
-                    latch.countDown();
-                }
-            }
-        };
-
-        mCM.registerNetworkCallback(request, callback);
-
-        try {
-            assertTrue("Private DNS hostname was not " + hostname + " after " + TIMEOUT_MS + "ms",
-                    latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            mCM.unregisterNetworkCallback(callback);
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
+            // Wait for private DNS setting to propagate.
+            mCtsNetUtils.awaitPrivateDnsSetting("Test wait private DNS setting timeout",
+                    network, hostname, false);
         }
     }
 
     private void setAndVerifyPrivateDns(boolean strictMode) throws Exception {
-        final ContentResolver cr = getInstrumentation().getContext().getContentResolver();
+        final ContentResolver cr = mTestContext.getContentResolver();
         String privateDnsHostname;
 
         if (strictMode) {
@@ -839,8 +868,13 @@
         callback.eventuallyExpect(RecorderCallback.CallbackEntry.NETWORK_CAPS_UPDATED,
                 NETWORK_CALLBACK_TIMEOUT_MS,
                 entry -> (Objects.equals(expectUnderlyingNetworks,
-                        ((RecorderCallback.CallbackEntry.CapabilitiesChanged) entry)
-                                .getCaps().getUnderlyingNetworks())));
+                        entry.getCaps().getUnderlyingNetworks())));
+    }
+
+    private void expectVpnNetwork(TestableNetworkCallback callback) {
+        callback.eventuallyExpect(RecorderCallback.CallbackEntry.NETWORK_CAPS_UPDATED,
+                NETWORK_CALLBACK_TIMEOUT_MS,
+                entry -> entry.getCaps().hasTransport(TRANSPORT_VPN));
     }
 
     @Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
@@ -866,7 +900,7 @@
                     false /* isAlwaysMetered */);
             // Acquire the NETWORK_SETTINGS permission for getting the underlying networks.
             runWithShellPermissionIdentity(() -> {
-                mCM.registerNetworkCallback(makeVpnNetworkRequest(), callback);
+                registerNetworkCallback(makeVpnNetworkRequest(), callback);
                 // Check that this VPN doesn't have any underlying networks.
                 expectUnderlyingNetworks(callback, new ArrayList<Network>());
 
@@ -899,8 +933,6 @@
                 } else {
                     mCtsNetUtils.ensureWifiDisconnected(null);
                 }
-            }, () -> {
-                mCM.unregisterNetworkCallback(callback);
             });
     }
 
@@ -921,7 +953,7 @@
         }
 
         final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(
-                getInstrumentation().getTargetContext(), MyVpnService.ACTION_ESTABLISHED);
+                mTargetContext, MyVpnService.ACTION_ESTABLISHED);
         receiver.register();
 
         // Test the behaviour of a variety of types of network callbacks.
@@ -934,9 +966,9 @@
                     UserHandle.of(5 /* userId */).getUid(Process.FIRST_APPLICATION_UID);
             final Handler h = new Handler(Looper.getMainLooper());
             runWithShellPermissionIdentity(() -> {
-                mCM.registerSystemDefaultNetworkCallback(systemDefaultCallback, h);
-                mCM.registerDefaultNetworkCallbackForUid(otherUid, otherUidCallback, h);
-                mCM.registerDefaultNetworkCallbackForUid(Process.myUid(), myUidCallback, h);
+                registerSystemDefaultNetworkCallback(systemDefaultCallback, h);
+                registerDefaultNetworkCallbackForUid(otherUid, otherUidCallback, h);
+                registerDefaultNetworkCallbackForUid(Process.myUid(), myUidCallback, h);
             }, NETWORK_SETTINGS);
             for (TestableNetworkCallback callback :
                     List.of(systemDefaultCallback, otherUidCallback, myUidCallback)) {
@@ -987,9 +1019,6 @@
             // fail and could cause the default network to switch (e.g., from wifi to cellular).
             systemDefaultCallback.assertNoCallback();
             otherUidCallback.assertNoCallback();
-            mCM.unregisterNetworkCallback(systemDefaultCallback);
-            mCM.unregisterNetworkCallback(otherUidCallback);
-            mCM.unregisterNetworkCallback(myUidCallback);
         }
 
         checkStrictModePrivateDns();
@@ -1018,6 +1047,183 @@
         checkStrictModePrivateDns();
     }
 
+    private int getSupportedKeepalives(NetworkCapabilities nc) throws Exception {
+        // Get number of supported concurrent keepalives for testing network.
+        final int[] keepalivesPerTransport = KeepaliveUtils.getSupportedKeepalives(
+                mTargetContext);
+        return KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(
+                keepalivesPerTransport, nc);
+    }
+
+    // This class can't be private, otherwise the constants can't be static imported.
+    static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback {
+        // This must be larger than the alarm delay in AutomaticOnOffKeepaliveTracker.
+        private static final int KEEPALIVE_TIMEOUT_MS = 10_000;
+        public enum CallbackType {
+            ON_STARTED,
+            ON_RESUMED,
+            ON_STOPPED,
+            ON_PAUSED,
+            ON_ERROR,
+            ON_DATA_RECEIVED
+        }
+        private ArrayTrackRecord<CallbackType> mHistory = new ArrayTrackRecord<>();
+        private ArrayTrackRecord<CallbackType>.ReadHead mEvents = mHistory.newReadHead();
+
+        @Override
+        public void onStarted() {
+            mHistory.add(ON_STARTED);
+        }
+
+        @Override
+        public void onResumed() {
+            mHistory.add(ON_RESUMED);
+        }
+
+        @Override
+        public void onStopped() {
+            mHistory.add(ON_STOPPED);
+        }
+
+        @Override
+        public void onPaused() {
+            mHistory.add(ON_PAUSED);
+        }
+
+        @Override
+        public void onError(final int error) {
+            mHistory.add(ON_ERROR);
+        }
+
+        @Override
+        public void onDataReceived() {
+            mHistory.add(ON_DATA_RECEIVED);
+        }
+
+        public CallbackType poll() {
+            return mEvents.poll(KEEPALIVE_TIMEOUT_MS, it -> true);
+        }
+    }
+
+    private InetAddress getV4AddrByName(final String hostname) throws Exception {
+        final InetAddress[] allAddrs = InetAddress.getAllByName(hostname);
+        for (InetAddress addr : allAddrs) {
+            if (addr instanceof Inet4Address) return addr;
+        }
+        return null;
+    }
+
+    @Test
+    public void testAutomaticOnOffKeepaliveModeNoClose() throws Exception {
+        doTestAutomaticOnOffKeepaliveMode(false);
+    }
+
+    @Test
+    public void testAutomaticOnOffKeepaliveModeClose() throws Exception {
+        doTestAutomaticOnOffKeepaliveMode(true);
+    }
+
+    private void startKeepalive(SocketKeepalive kp, TestSocketKeepaliveCallback callback) {
+        runWithShellPermissionIdentity(() -> {
+            // Only SocketKeepalive.start() requires READ_DEVICE_CONFIG because feature is protected
+            // by a feature flag. But also verify ON_STARTED callback received here to ensure
+            // keepalive is indeed started because start() runs in the executor thread and shell
+            // permission may be dropped before reading DeviceConfig.
+            kp.start(10 /* intervalSec */, SocketKeepalive.FLAG_AUTOMATIC_ON_OFF, mNetwork);
+
+            // Verify callback status.
+            assertEquals(ON_STARTED, callback.poll());
+        }, READ_DEVICE_CONFIG);
+    }
+
+    private void doTestAutomaticOnOffKeepaliveMode(final boolean closeSocket) throws Exception {
+        assumeTrue(supportedHardware());
+
+        // Get default network first before starting VPN
+        final Network defaultNetwork = mCM.getActiveNetwork();
+        final TestableNetworkCallback cb = new TestableNetworkCallback();
+        registerDefaultNetworkCallback(cb);
+        cb.expect(CallbackEntry.AVAILABLE, defaultNetwork);
+        final NetworkCapabilities cap =
+                cb.expect(CallbackEntry.NETWORK_CAPS_UPDATED, defaultNetwork).getCaps();
+        final LinkProperties lp =
+                cb.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, defaultNetwork).getLp();
+        cb.expect(CallbackEntry.BLOCKED_STATUS, defaultNetwork);
+
+        // Setup VPN
+        final FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
+        final String allowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
+        startVpn(new String[]{"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
+                new String[]{"192.0.2.0/24", "2001:db8::/32"},
+                allowedApps, "" /* disallowedApplications */, null /* proxyInfo */,
+                null /* underlyingNetworks */, false /* isAlwaysMetered */);
+        assertSocketClosed(fd, TEST_HOST);
+
+        // Decrease the TCP polling timer for testing.
+        runWithShellPermissionIdentity(() -> mCM.setTestLowTcpPollingTimerForKeepalive(
+                System.currentTimeMillis() + TEST_TCP_POLLING_TIMER_EXPIRED_PERIOD_MS),
+                NETWORK_SETTINGS);
+
+        // Setup keepalive
+        final int supported = getSupportedKeepalives(cap);
+        assumeTrue("Network " + defaultNetwork + " does not support keepalive", supported != 0);
+        final InetAddress srcAddr = CollectionUtils.findFirst(lp.getAddresses(),
+                it -> it instanceof Inet4Address);
+        assumeTrue("This test requires native IPv4", srcAddr != null);
+
+        final TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
+
+        final String origMode = runWithShellPermissionIdentity(() -> {
+            final String mode = DeviceConfig.getProperty(
+                    DeviceConfig.NAMESPACE_CONNECTIVITY, AUTOMATIC_ON_OFF_KEEPALIVE_VERSION);
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY,
+                    AUTOMATIC_ON_OFF_KEEPALIVE_VERSION,
+                    AUTOMATIC_ON_OFF_KEEPALIVE_ENABLED, false /* makeDefault */);
+            return mode;
+        }, READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG);
+
+        final IpSecManager ipSec = mTargetContext.getSystemService(IpSecManager.class);
+        SocketKeepalive kp = null;
+        try (IpSecManager.UdpEncapsulationSocket nattSocket = ipSec.openUdpEncapsulationSocket()) {
+            final InetAddress dstAddr = getV4AddrByName(TEST_HOST);
+            assertNotNull(dstAddr);
+
+            // Start keepalive with dynamic keepalive mode enabled.
+            final Executor executor = mTargetContext.getMainExecutor();
+            kp = mCM.createSocketKeepalive(defaultNetwork, nattSocket,
+                    srcAddr, dstAddr, executor, callback);
+            startKeepalive(kp, callback);
+
+            // There should be no open sockets on the VPN network, because any
+            // open sockets were closed when startVpn above was called. So the
+            // first TCP poll should trigger ON_PAUSED.
+            assertEquals(ON_PAUSED, callback.poll());
+
+            final Socket s = new Socket();
+            mNetwork.bindSocket(s);
+            s.connect(new InetSocketAddress(dstAddr, 80));
+            assertEquals(ON_RESUMED, callback.poll());
+
+            if (closeSocket) {
+                s.close();
+                assertEquals(ON_PAUSED, callback.poll());
+            }
+
+            kp.stop();
+            assertEquals(ON_STOPPED, callback.poll());
+        } finally {
+            if (kp != null) kp.stop();
+
+            runWithShellPermissionIdentity(() -> {
+                DeviceConfig.setProperty(
+                                DeviceConfig.NAMESPACE_CONNECTIVITY,
+                                AUTOMATIC_ON_OFF_KEEPALIVE_VERSION,
+                                origMode, false);
+                mCM.setTestLowTcpPollingTimerForKeepalive(0);
+            }, WRITE_DEVICE_CONFIG, NETWORK_SETTINGS);
+        }
+    }
+
     @Test
     public void testAppDisallowed() throws Exception {
         assumeTrue(supportedHardware());
@@ -1053,6 +1259,31 @@
     }
 
     @Test
+    public void testSocketClosed() throws Exception {
+        assumeTrue(supportedHardware());
+
+        final FileDescriptor localFd = openSocketFd(TEST_HOST, 80, TIMEOUT_MS);
+        final List<FileDescriptor> remoteFds = new ArrayList<>();
+
+        for (int i = 0; i < 30; i++) {
+            remoteFds.add(openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS));
+        }
+
+        final String allowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
+        startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
+                new String[] {"192.0.2.0/24", "2001:db8::/32"},
+                allowedApps, "", null, null /* underlyingNetworks */, false /* isAlwaysMetered */);
+
+        // Socket owned by VPN uid is not closed
+        assertSocketStillOpen(localFd, TEST_HOST);
+
+        // Sockets not owned by VPN uid are closed
+        for (final FileDescriptor remoteFd: remoteFds) {
+            assertSocketClosed(remoteFd, TEST_HOST);
+        }
+    }
+
+    @Test
     public void testExcludedRoutes() throws Exception {
         assumeTrue(supportedHardware());
         assumeTrue(SdkLevel.isAtLeastT());
@@ -1517,7 +1748,7 @@
         private boolean received;
 
         public ProxyChangeBroadcastReceiver() {
-            super(getInstrumentation().getContext(), Proxy.PROXY_CHANGE_ACTION);
+            super(mTestContext, Proxy.PROXY_CHANGE_ACTION);
             received = false;
         }
 
@@ -1547,12 +1778,11 @@
                 "" /* allowedApps */, "com.android.providers.downloads", null /* proxyInfo */,
                 null /* underlyingNetworks */, false /* isAlwaysMetered */);
 
-        final Context context = getInstrumentation().getContext();
-        final DownloadManager dm = context.getSystemService(DownloadManager.class);
+        final DownloadManager dm = mTestContext.getSystemService(DownloadManager.class);
         final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver();
         try {
             final int flags = SdkLevel.isAtLeastT() ? RECEIVER_EXPORTED : 0;
-            context.registerReceiver(receiver,
+            mTestContext.registerReceiver(receiver,
                     new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), flags);
 
             // Enqueue a request and check only one download.
@@ -1570,7 +1800,7 @@
             assertEquals(1, dm.remove(id));
             assertEquals(0, getTotalNumberDownloads(dm, new Query()));
         } finally {
-            context.unregisterReceiver(receiver);
+            mTestContext.unregisterReceiver(receiver);
         }
     }
 
@@ -1607,8 +1837,7 @@
 
         // Create a TUN interface
         final FileDescriptor tunFd = runWithShellPermissionIdentity(() -> {
-            final TestNetworkManager tnm = getInstrumentation().getContext().getSystemService(
-                    TestNetworkManager.class);
+            final TestNetworkManager tnm = mTestContext.getSystemService(TestNetworkManager.class);
             final TestNetworkInterface iface = tnm.createTunInterface(List.of(
                     TEST_IP4_DST_ADDR, TEST_IP6_DST_ADDR));
             return iface.getFileDescriptor().getFileDescriptor();
@@ -1619,10 +1848,10 @@
 
         testAndCleanup(() -> {
             runWithShellPermissionIdentity(() -> {
-                mCM.registerDefaultNetworkCallbackForUid(remoteUid, remoteUidCallback,
+                registerDefaultNetworkCallbackForUid(remoteUid, remoteUidCallback,
                         new Handler(Looper.getMainLooper()));
             }, NETWORK_SETTINGS);
-            remoteUidCallback.expectAvailableCallbacks(network);
+            remoteUidCallback.expectAvailableCallbacksWithBlockedReasonNone(network);
 
             // The remote UDP socket can receive packets coming from the TUN interface
             checkBlockIncomingPacket(tunFd, remoteUdpFd, EXPECT_PASS);
@@ -1635,7 +1864,8 @@
             // setRequireVpnForUids setup a lockdown rule asynchronously. So it needs to wait for
             // BlockedStatusCallback to be fired before checking the blocking status of incoming
             // packets.
-            remoteUidCallback.expectBlockedStatusCallback(network, BLOCKED_REASON_LOCKDOWN_VPN);
+            remoteUidCallback.expect(BLOCKED_STATUS_INT, network,
+                    cb -> cb.getReason() == BLOCKED_REASON_LOCKDOWN_VPN);
 
             if (SdkLevel.isAtLeastT()) {
                 // On T and above, lockdown rule drop packets not coming from lo regardless of the
@@ -1654,8 +1884,6 @@
 
             checkBlockIncomingPacket(tunFd, remoteUdpFd, EXPECT_BLOCK);
         }, /* cleanup */ () -> {
-                mCM.unregisterNetworkCallback(remoteUidCallback);
-            }, /* cleanup */ () -> {
                 Os.close(tunFd);
             }, /* cleanup */ () -> {
                 Os.close(remoteUdpFd);
@@ -1666,6 +1894,54 @@
             });
     }
 
+    @Test
+    public void testSetVpnDefaultForUids() throws Exception {
+        assumeTrue(supportedHardware());
+        assumeTrue(SdkLevel.isAtLeastU());
+
+        final Network defaultNetwork = mCM.getActiveNetwork();
+        assertNotNull("There must be a default network", defaultNetwork);
+
+        final TestableNetworkCallback defaultNetworkCallback = new TestableNetworkCallback();
+        final String session = UUID.randomUUID().toString();
+        final int myUid = Process.myUid();
+
+        testAndCleanup(() -> {
+            registerDefaultNetworkCallback(defaultNetworkCallback);
+            defaultNetworkCallback.expectAvailableCallbacks(defaultNetwork);
+
+            final Range<Integer> myUidRange = new Range<>(myUid, myUid);
+            runWithShellPermissionIdentity(() -> {
+                mCM.setVpnDefaultForUids(session, List.of(myUidRange));
+            }, NETWORK_SETTINGS);
+
+            // The VPN will be the only default network for the app, so it's expected to receive
+            // onLost() callback.
+            defaultNetworkCallback.eventuallyExpect(CallbackEntry.LOST);
+
+            final ArrayList<Network> underlyingNetworks = new ArrayList<>();
+            underlyingNetworks.add(defaultNetwork);
+            startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"} /* addresses */,
+                    new String[] {"0.0.0.0/0", "::/0"} /* routes */,
+                    "" /* allowedApplications */, "" /* disallowedApplications */,
+                    null /* proxyInfo */, underlyingNetworks, false /* isAlwaysMetered */);
+
+            expectVpnNetwork(defaultNetworkCallback);
+        }, /* cleanup */ () -> {
+                stopVpn();
+                defaultNetworkCallback.eventuallyExpect(CallbackEntry.LOST);
+            }, /* cleanup */ () -> {
+                runWithShellPermissionIdentity(() -> {
+                    mCM.setVpnDefaultForUids(session, new ArraySet<>());
+                }, NETWORK_SETTINGS);
+                // The default network of the app will be changed back to wifi when the VPN network
+                // preference feature is disabled.
+                defaultNetworkCallback.eventuallyExpect(CallbackEntry.AVAILABLE,
+                        NETWORK_CALLBACK_TIMEOUT_MS,
+                        entry -> defaultNetwork.equals(entry.getNetwork()));
+            });
+    }
+
     private ByteBuffer buildIpv4UdpPacket(final Inet4Address dstAddr, final Inet4Address srcAddr,
             final short dstPort, final short srcPort, final byte[] payload) throws IOException {
 
@@ -1756,13 +2032,10 @@
     }
 
     private class DetailedBlockedStatusCallback extends TestableNetworkCallback {
-        public void expectAvailableCallbacks(Network network) {
+        public void expectAvailableCallbacksWithBlockedReasonNone(Network network) {
             super.expectAvailableCallbacks(network, false /* suspended */, true /* validated */,
                     BLOCKED_REASON_NONE, NETWORK_CALLBACK_TIMEOUT_MS);
         }
-        public void expectBlockedStatusCallback(Network network, int blockedStatus) {
-            super.expectBlockedStatusCallback(blockedStatus, network, NETWORK_CALLBACK_TIMEOUT_MS);
-        }
         public void onBlockedStatusChanged(Network network, int blockedReasons) {
             getHistory().add(new CallbackEntry.BlockedStatusInt(network, blockedReasons));
         }
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java
index 2e79182..37dc7a0 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java
@@ -99,7 +99,8 @@
             }
             case TYPE_COMPONENT_EXPEDITED_JOB: {
                 final int capabilities = activityManager.getUidProcessCapabilities(Process.myUid());
-                if ((capabilities & ActivityManager.PROCESS_CAPABILITY_NETWORK) == 0) {
+                if ((capabilities
+                        & ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) == 0) {
                     observer.onNetworkStateChecked(
                             INetworkStateObserver.RESULT_ERROR_UNEXPECTED_CAPABILITIES,
                             "Unexpected capabilities: " + capabilities);
diff --git a/tests/cts/hostside/networkslicingtestapp/Android.bp b/tests/cts/hostside/networkslicingtestapp/Android.bp
new file mode 100644
index 0000000..2aa3f69
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/Android.bp
@@ -0,0 +1,65 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_defaults {
+    name: "CtsHostsideNetworkCapTestsAppDefaults",
+    platform_apis: true,
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "modules-utils-build",
+        "cts-net-utils",
+    ],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "sts",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsHostsideNetworkCapTestsAppWithoutProperty",
+    defaults: [
+           "cts_support_defaults",
+           "CtsHostsideNetworkCapTestsAppDefaults"
+    ],
+    manifest: "AndroidManifestWithoutProperty.xml",
+}
+
+android_test_helper_app {
+    name: "CtsHostsideNetworkCapTestsAppWithProperty",
+    defaults: [
+           "cts_support_defaults",
+           "CtsHostsideNetworkCapTestsAppDefaults"
+    ],
+    manifest: "AndroidManifestWithProperty.xml",
+}
+
+android_test_helper_app {
+    name: "CtsHostsideNetworkCapTestsAppSdk33",
+    defaults: [
+           "cts_support_defaults",
+           "CtsHostsideNetworkCapTestsAppDefaults"
+    ],
+    target_sdk_version: "33",
+    manifest: "AndroidManifestWithoutProperty.xml",
+}
diff --git a/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithProperty.xml b/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithProperty.xml
new file mode 100644
index 0000000..3ef0376
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithProperty.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.cts.net.hostside.networkslicingtestapp">
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+        <property android:name="android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES"
+                  android:resource="@xml/self_certified_network_capabilities_both" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.net.hostside.networkslicingtestapp"/>
+
+</manifest>
diff --git a/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithoutProperty.xml b/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithoutProperty.xml
new file mode 100644
index 0000000..fe66684
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithoutProperty.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.cts.net.hostside.networkslicingtestapp">
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.net.hostside.networkslicingtestapp"/>
+
+</manifest>
diff --git a/tests/cts/hostside/networkslicingtestapp/res/xml/self_certified_network_capabilities_both.xml b/tests/cts/hostside/networkslicingtestapp/res/xml/self_certified_network_capabilities_both.xml
new file mode 100644
index 0000000..4066be2
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/res/xml/self_certified_network_capabilities_both.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+</network-capabilities-declaration>
diff --git a/tests/cts/hostside/networkslicingtestapp/src/com.android.cts.net.hostside.networkslicingtestapp/NetworkSelfDeclaredCapabilitiesTest.java b/tests/cts/hostside/networkslicingtestapp/src/com.android.cts.net.hostside.networkslicingtestapp/NetworkSelfDeclaredCapabilitiesTest.java
new file mode 100644
index 0000000..39792fc
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/src/com.android.cts.net.hostside.networkslicingtestapp/NetworkSelfDeclaredCapabilitiesTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net.hostside.networkslicingtestapp;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.Build;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class NetworkSelfDeclaredCapabilitiesTest {
+
+    @Rule
+    public final DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule();
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void requestNetwork_withoutRequestCapabilities() {
+        final ConnectivityManager cm =
+                (ConnectivityManager)
+                        InstrumentationRegistry.getInstrumentation()
+                                .getContext()
+                                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        final NetworkRequest request =
+                new NetworkRequest.Builder().build();
+        final ConnectivityManager.NetworkCallback callback =
+                new ConnectivityManager.NetworkCallback();
+        cm.requestNetwork(request, callback);
+        cm.unregisterNetworkCallback(callback);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void requestNetwork_withSelfDeclaredCapabilities() {
+        final ConnectivityManager cm =
+                (ConnectivityManager)
+                        InstrumentationRegistry.getInstrumentation()
+                                .getContext()
+                                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        final NetworkRequest request =
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                        .build();
+        final ConnectivityManager.NetworkCallback callback =
+                new ConnectivityManager.NetworkCallback();
+        cm.requestNetwork(request, callback);
+        cm.unregisterNetworkCallback(callback);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void requestNetwork_lackingRequiredSelfDeclaredCapabilities() {
+        final ConnectivityManager cm =
+                (ConnectivityManager)
+                        InstrumentationRegistry.getInstrumentation()
+                                .getContext()
+                                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        final NetworkRequest request =
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                        .build();
+        final ConnectivityManager.NetworkCallback callback =
+                new ConnectivityManager.NetworkCallback();
+        assertThrows(
+                SecurityException.class,
+                () -> cm.requestNetwork(request, callback));
+    }
+
+}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
index d0567ae..2aa1032 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -31,6 +31,7 @@
 import com.android.tradefed.testtype.IAbi;
 import com.android.tradefed.testtype.IAbiReceiver;
 import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.RunUtil;
 
 import java.io.FileNotFoundException;
 import java.util.Map;
@@ -120,7 +121,7 @@
             i++;
             Log.v(TAG, "Package " + packageName + " not uninstalled yet (" + result
                     + "); sleeping 1s before polling again");
-            Thread.sleep(1000);
+            RunUtil.getDefault().sleep(1000);
         }
         fail("Package '" + packageName + "' not uinstalled after " + max_tries + " seconds");
     }
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
index 7a613b3..21c78b7 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
@@ -20,6 +20,7 @@
 
 import com.android.ddmlib.Log;
 import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.util.RunUtil;
 
 public class HostsideRestrictBackgroundNetworkTests extends HostsideNetworkTestCase {
 
@@ -359,7 +360,7 @@
             }
             Log.v(TAG, "whitelist check for uid " + uid + " doesn't match yet (expected "
                     + expected + ", got " + actual + "); sleeping 1s before polling again");
-            Thread.sleep(1000);
+            RunUtil.getDefault().sleep(1000);
         }
         fail("whitelist check for uid " + uid + " failed: expected "
                 + expected + ", got " + actual);
@@ -384,7 +385,7 @@
             if (result.equals(expectedResult)) return;
             Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
                     + expectedResult + "' on attempt #; sleeping 1s before polling again");
-            Thread.sleep(1000);
+            RunUtil.getDefault().sleep(1000);
         }
         fail("Command '" + command + "' did not return '" + expectedResult + "' after " + maxTries
                 + " attempts");
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideSelfDeclaredNetworkCapabilitiesCheckTest.java b/tests/cts/hostside/src/com/android/cts/net/HostsideSelfDeclaredNetworkCapabilitiesCheckTest.java
new file mode 100644
index 0000000..4c2985d
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideSelfDeclaredNetworkCapabilitiesCheckTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.net;
+
+public class HostsideSelfDeclaredNetworkCapabilitiesCheckTest extends HostsideNetworkTestCase {
+
+    private static final String TEST_WITH_PROPERTY_IN_CURRENT_SDK_APK =
+            "CtsHostsideNetworkCapTestsAppWithProperty.apk";
+    private static final String TEST_WITHOUT_PROPERTY_IN_CURRENT_SDK_APK =
+            "CtsHostsideNetworkCapTestsAppWithoutProperty.apk";
+    private static final String TEST_IN_SDK_33_APK =
+            "CtsHostsideNetworkCapTestsAppSdk33.apk";
+    private static final String TEST_APP_PKG =
+            "com.android.cts.net.hostside.networkslicingtestapp";
+    private static final String TEST_CLASS_NAME = ".NetworkSelfDeclaredCapabilitiesTest";
+    private static final String WITH_SELF_DECLARED_CAPABILITIES_METHOD =
+            "requestNetwork_withSelfDeclaredCapabilities";
+    private static final String LACKING_SELF_DECLARED_CAPABILITIES_METHOD =
+            "requestNetwork_lackingRequiredSelfDeclaredCapabilities";
+    private static final String WITHOUT_REQUEST_CAPABILITIES_METHOD =
+            "requestNetwork_withoutRequestCapabilities";
+
+
+    public void testRequestNetworkInCurrentSdkWithProperty() throws Exception {
+        uninstallPackage(TEST_APP_PKG, false);
+        installPackage(TEST_WITH_PROPERTY_IN_CURRENT_SDK_APK);
+        // If the self-declared capabilities are defined,
+        // the ConnectivityManager.requestNetwork() call should always pass.
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                WITH_SELF_DECLARED_CAPABILITIES_METHOD);
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                WITHOUT_REQUEST_CAPABILITIES_METHOD);
+        uninstallPackage(TEST_APP_PKG, true);
+    }
+
+    public void testRequestNetworkInCurrentSdkWithoutProperty() throws Exception {
+        uninstallPackage(TEST_APP_PKG, false);
+        installPackage(TEST_WITHOUT_PROPERTY_IN_CURRENT_SDK_APK);
+        // If the self-declared capabilities are not defined,
+        // the ConnectivityManager.requestNetwork() call will fail if the properly is not declared.
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                LACKING_SELF_DECLARED_CAPABILITIES_METHOD);
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                WITHOUT_REQUEST_CAPABILITIES_METHOD);
+        uninstallPackage(TEST_APP_PKG, true);
+    }
+
+    public void testRequestNetworkInSdk33() throws Exception {
+        uninstallPackage(TEST_APP_PKG, false);
+        installPackage(TEST_IN_SDK_33_APK);
+        // In Sdk33, the ConnectivityManager.requestNetwork() call should always pass.
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                WITH_SELF_DECLARED_CAPABILITIES_METHOD);
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                WITHOUT_REQUEST_CAPABILITIES_METHOD);
+        uninstallPackage(TEST_APP_PKG, true);
+    }
+
+    public void testReinstallPackageWillUpdateProperty() throws Exception {
+        uninstallPackage(TEST_APP_PKG, false);
+        installPackage(TEST_WITHOUT_PROPERTY_IN_CURRENT_SDK_APK);
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                LACKING_SELF_DECLARED_CAPABILITIES_METHOD);
+        uninstallPackage(TEST_APP_PKG, true);
+
+
+        // Updates package.
+        installPackage(TEST_WITH_PROPERTY_IN_CURRENT_SDK_APK);
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                WITH_SELF_DECLARED_CAPABILITIES_METHOD);
+        uninstallPackage(TEST_APP_PKG, true);
+
+    }
+}
+
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
index 4d90a4a..3ca4775 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.net;
 
+import android.platform.test.annotations.RequiresDevice;
+
 public class HostsideVpnTests extends HostsideNetworkTestCase {
 
     @Override
@@ -49,6 +51,10 @@
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testAppDisallowed");
     }
 
+    public void testSocketClosed() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSocketClosed");
+    }
+
     public void testGetConnectionOwnerUidSecurity() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testGetConnectionOwnerUidSecurity");
     }
@@ -89,6 +95,18 @@
                 TEST_PKG, TEST_PKG + ".VpnTest", "testAlwaysMeteredVpnWithNullUnderlyingNetwork");
     }
 
+    @RequiresDevice // Keepalive is not supported on virtual hardware
+    public void testAutomaticOnOffKeepaliveModeClose() throws Exception {
+        runDeviceTests(
+                TEST_PKG, TEST_PKG + ".VpnTest", "testAutomaticOnOffKeepaliveModeClose");
+    }
+
+    @RequiresDevice // Keepalive is not supported on virtual hardware
+    public void testAutomaticOnOffKeepaliveModeNoClose() throws Exception {
+        runDeviceTests(
+                TEST_PKG, TEST_PKG + ".VpnTest", "testAutomaticOnOffKeepaliveModeNoClose");
+    }
+
     public void testAlwaysMeteredVpnWithNonNullUnderlyingNetwork() throws Exception {
         runDeviceTests(
                 TEST_PKG,
@@ -120,4 +138,8 @@
     public void testBlockIncomingPackets() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testBlockIncomingPackets");
     }
+
+    public void testSetVpnDefaultForUids() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetVpnDefaultForUids");
+    }
 }
diff --git a/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java b/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
index 19e61c6..1a528b1 100644
--- a/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
+++ b/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
@@ -166,4 +166,15 @@
             assertTrue(interval <= upperBoundSec);
         }
     }
+
+    /**
+     * Verify that cubic is used as the congestion control algorithm.
+     * (This repeats the VTS test, and is here for good performance of the internet as a whole.)
+     * TODO: revisit this once a better CC algorithm like BBR2 is available.
+     */
+    public void testCongestionControl() throws Exception {
+        String path = "/proc/sys/net/ipv4/tcp_congestion_control";
+        String value = mDevice.executeAdbCommand("shell", "cat", path).trim();
+        assertEquals(value, "cubic");
+    }
 }
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 23cb15c..51ee074 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -61,7 +61,7 @@
     // uncomment when b/13249961 is fixed
     // sdk_version: "current",
     platform_apis: true,
-    data: [":ConnectivityChecker"],
+    data: [":ConnectivityTestPreparer"],
     per_testcase_directory: true,
     host_required: ["net-tests-utils-host-common"],
     test_config_template: "AndroidTestTemplate.xml",
@@ -114,34 +114,39 @@
     ],
 }
 
-android_test {
-    name: "CtsNetTestCasesMaxTargetSdk31",  // Must match CtsNetTestCasesMaxTargetSdk31 annotation.
+java_defaults {
+    name: "CtsNetTestCasesMaxTargetSdkDefaults",
     defaults: [
         "CtsNetTestCasesDefaults",
         "CtsNetTestCasesApiStableDefaults",
     ],
-    target_sdk_version: "31",
-    package_name: "android.net.cts.maxtargetsdk31",  // CTS package names must be unique.
-    instrumentation_target_package: "android.net.cts.maxtargetsdk31",
     test_suites: [
         "cts",
         "general-tests",
-        "mts-networking",
+        "mts-tethering",
     ],
 }
 
 android_test {
+    name: "CtsNetTestCasesMaxTargetSdk33",  // Must match CtsNetTestCasesMaxTargetSdk33 annotation.
+    defaults: ["CtsNetTestCasesMaxTargetSdkDefaults"],
+    target_sdk_version: "33",
+    package_name: "android.net.cts.maxtargetsdk33",
+    instrumentation_target_package: "android.net.cts.maxtargetsdk33",
+}
+
+android_test {
+    name: "CtsNetTestCasesMaxTargetSdk31",  // Must match CtsNetTestCasesMaxTargetSdk31 annotation.
+    defaults: ["CtsNetTestCasesMaxTargetSdkDefaults"],
+    target_sdk_version: "31",
+    package_name: "android.net.cts.maxtargetsdk31",  // CTS package names must be unique.
+    instrumentation_target_package: "android.net.cts.maxtargetsdk31",
+}
+
+android_test {
     name: "CtsNetTestCasesMaxTargetSdk30",  // Must match CtsNetTestCasesMaxTargetSdk30 annotation.
-    defaults: [
-        "CtsNetTestCasesDefaults",
-        "CtsNetTestCasesApiStableDefaults",
-    ],
+    defaults: ["CtsNetTestCasesMaxTargetSdkDefaults"],
     target_sdk_version: "30",
     package_name: "android.net.cts.maxtargetsdk30",  // CTS package names must be unique.
     instrumentation_target_package: "android.net.cts.maxtargetsdk30",
-    test_suites: [
-        "cts",
-        "general-tests",
-        "mts-networking",
-    ],
 }
diff --git a/tests/cts/net/AndroidManifest.xml b/tests/cts/net/AndroidManifest.xml
index 25490da..68e36ff 100644
--- a/tests/cts/net/AndroidManifest.xml
+++ b/tests/cts/net/AndroidManifest.xml
@@ -37,9 +37,6 @@
     <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
 
-    <!-- TODO (b/186093901): remove after fixing resource querying -->
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
-
     <!-- This test also uses signature permissions through adopting the shell identity.
          The permissions acquired that way include (probably not exhaustive) :
              android.permission.MANAGE_TEST_NETWORKS
@@ -54,8 +51,6 @@
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.net.cts"
                      android:label="CTS tests of android.net">
-        <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
     </instrumentation>
 
 </manifest>
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index d2fb04a..8efa99f 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -28,7 +28,7 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="{MODULE}.apk" />
     </target_preparer>
-    <target_preparer class="com.android.testutils.ConnectivityCheckTargetPreparer">
+    <target_preparer class="com.android.testutils.ConnectivityTestTargetPreparer">
     </target_preparer>
     <target_preparer class="com.android.testutils.DisableConfigSyncTargetPreparer">
     </target_preparer>
diff --git a/tests/cts/net/api23Test/AndroidManifest.xml b/tests/cts/net/api23Test/AndroidManifest.xml
index 69ee0dd..44c63f6 100644
--- a/tests/cts/net/api23Test/AndroidManifest.xml
+++ b/tests/cts/net/api23Test/AndroidManifest.xml
@@ -39,7 +39,5 @@
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
          android:targetPackage="android.net.cts.api23test"
          android:label="CTS tests of android.net">
-        <meta-data android:name="listener"
-             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 </manifest>
diff --git a/tests/cts/net/native/dns/Android.bp b/tests/cts/net/native/dns/Android.bp
index 434e529..49b9337 100644
--- a/tests/cts/net/native/dns/Android.bp
+++ b/tests/cts/net/native/dns/Android.bp
@@ -24,6 +24,10 @@
         "liblog",
         "libutils",
     ],
+    static_libs: [
+        "libbase",
+        "libnetdutils",
+    ],
     // To be compatible with Q devices, the min_sdk_version must be 29.
     min_sdk_version: "29",
 }
diff --git a/tests/cts/net/native/dns/NativeDnsAsyncTest.cpp b/tests/cts/net/native/dns/NativeDnsAsyncTest.cpp
index e501475..68bd227 100644
--- a/tests/cts/net/native/dns/NativeDnsAsyncTest.cpp
+++ b/tests/cts/net/native/dns/NativeDnsAsyncTest.cpp
@@ -28,6 +28,7 @@
 
 #include <android/multinetwork.h>
 #include <gtest/gtest.h>
+#include <netdutils/NetNativeTestBase.h>
 
 namespace {
 constexpr int MAXPACKET = 8 * 1024;
@@ -101,7 +102,9 @@
 
 } // namespace
 
-TEST (NativeDnsAsyncTest, Async_Query) {
+class NativeDnsAsyncTest : public NetNativeTestBase {};
+
+TEST_F(NativeDnsAsyncTest, Async_Query) {
     // V4
     int fd1 = android_res_nquery(
             NETWORK_UNSPECIFIED, "www.google.com", ns_c_in, ns_t_a, 0);
@@ -123,7 +126,7 @@
     expectAnswersValid(fd1, AF_INET6, ns_r_noerror);
 }
 
-TEST (NativeDnsAsyncTest, Async_Send) {
+TEST_F(NativeDnsAsyncTest, Async_Send) {
     // V4
     uint8_t buf1[MAXPACKET] = {};
     int len1 = res_mkquery(ns_o_query, "www.googleapis.com",
@@ -162,7 +165,7 @@
     expectAnswersValid(fd1, AF_INET6, ns_r_noerror);
 }
 
-TEST (NativeDnsAsyncTest, Async_NXDOMAIN) {
+TEST_F(NativeDnsAsyncTest, Async_NXDOMAIN) {
     uint8_t buf[MAXPACKET] = {};
     int len = res_mkquery(ns_o_query, "test1-nx.metric.gstatic.com",
             ns_c_in, ns_t_a, nullptr, 0, nullptr, buf, sizeof(buf));
@@ -191,7 +194,7 @@
     expectAnswersValid(fd1, AF_INET6, ns_r_nxdomain);
 }
 
-TEST (NativeDnsAsyncTest, Async_Cancel) {
+TEST_F(NativeDnsAsyncTest, Async_Cancel) {
     int fd = android_res_nquery(
             NETWORK_UNSPECIFIED, "www.google.com", ns_c_in, ns_t_a, 0);
     errno = 0;
@@ -202,7 +205,7 @@
     // otherwise it will hit fdsan double-close fd.
 }
 
-TEST (NativeDnsAsyncTest, Async_Query_MALFORMED) {
+TEST_F(NativeDnsAsyncTest, Async_Query_MALFORMED) {
     // Empty string to create BLOB and query, we will get empty result and rcode = 0
     // on DNSTLS.
     int fd = android_res_nquery(
@@ -221,7 +224,7 @@
     EXPECT_EQ(-EMSGSIZE, fd);
 }
 
-TEST (NativeDnsAsyncTest, Async_Send_MALFORMED) {
+TEST_F(NativeDnsAsyncTest, Async_Send_MALFORMED) {
     uint8_t buf[10] = {};
     // empty BLOB
     int fd = android_res_nsend(NETWORK_UNSPECIFIED, buf, 10, 0);
diff --git a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
index 7c24c95..dc22369 100644
--- a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
+++ b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
@@ -37,8 +37,6 @@
 import android.net.cts.NetworkValidationTestUtil.setHttpsUrlDeviceConfig
 import android.net.cts.NetworkValidationTestUtil.setUrlExpirationDeviceConfig
 import android.net.cts.util.CtsNetUtils
-import com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTPS_URL
-import com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTP_URL
 import android.platform.test.annotations.AppModeFull
 import android.provider.DeviceConfig
 import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
@@ -47,28 +45,30 @@
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import androidx.test.runner.AndroidJUnit4
 import com.android.modules.utils.build.SdkLevel.isAtLeastR
+import com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTPS_URL
+import com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTP_URL
 import com.android.testutils.DeviceConfigRule
-import com.android.testutils.RecorderCallback
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
 import com.android.testutils.TestHttpServer
 import com.android.testutils.TestHttpServer.Request
 import com.android.testutils.TestableNetworkCallback
 import com.android.testutils.runAsShell
 import fi.iki.elonen.NanoHTTPD.Response.Status
-import junit.framework.AssertionFailedError
-import org.junit.After
-import org.junit.Assume.assumeTrue
-import org.junit.Assume.assumeFalse
-import org.junit.Before
-import org.junit.BeforeClass
-import org.junit.Rule
-import org.junit.runner.RunWith
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.TimeoutException
+import junit.framework.AssertionFailedError
 import kotlin.test.Test
 import kotlin.test.assertNotEquals
 import kotlin.test.assertNotNull
 import kotlin.test.assertTrue
+import org.junit.After
+import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Rule
+import org.junit.runner.RunWith
 
 private const val TEST_HTTPS_URL_PATH = "/https_path"
 private const val TEST_HTTP_URL_PATH = "/http_path"
@@ -151,8 +151,8 @@
                 .build()
         val cellCb = TestableNetworkCallback(timeoutMs = TEST_TIMEOUT_MS)
         cm.registerNetworkCallback(cellReq, cellCb)
-        val cb = cellCb.eventuallyExpectOrNull<RecorderCallback.CallbackEntry.CapabilitiesChanged> {
-            it.network == cellNetwork && it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
+        val cb = cellCb.poll { it.network == cellNetwork &&
+                it is CapabilitiesChanged && it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
         }
         assertNotNull(cb, "Mobile network $cellNetwork has no access to the internet. " +
                 "Check the mobile data connection.")
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
index 7662ba3..60befca 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
@@ -117,7 +117,7 @@
     private static final int FAIL_RATE_PERCENTAGE = 100;
     private static final int UNKNOWN_DETECTION_METHOD = 4;
     private static final int FILTERED_UNKNOWN_DETECTION_METHOD = 0;
-    private static final int CARRIER_CONFIG_CHANGED_BROADCAST_TIMEOUT = 5000;
+    private static final int CARRIER_CONFIG_CHANGED_BROADCAST_TIMEOUT = 10000;
     private static final int DELAY_FOR_BROADCAST_IDLE = 30_000;
 
     private static final Executor INLINE_EXECUTOR = x -> x.run();
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 96acac3..ee2f6bb 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -96,6 +96,7 @@
 import static com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTP_URL;
 import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_LOCKDOWN_VPN;
 import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_NONE;
+import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_EXPORTED;
 import static com.android.testutils.Cleanup.testAndCleanup;
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 import static com.android.testutils.MiscAsserts.assertThrows;
@@ -224,6 +225,7 @@
 import java.net.InetSocketAddress;
 import java.net.MalformedURLException;
 import java.net.Socket;
+import java.net.SocketException;
 import java.net.URL;
 import java.net.UnknownHostException;
 import java.nio.charset.StandardCharsets;
@@ -233,6 +235,7 @@
 import java.util.Objects;
 import java.util.Random;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
@@ -276,6 +279,7 @@
     // TODO(b/252972908): reset the original timer when aosp/2188755 is ramped up.
     private static final int LISTEN_ACTIVITY_TIMEOUT_MS = 30_000;
     private static final int NO_CALLBACK_TIMEOUT_MS = 100;
+    private static final int NETWORK_REQUEST_TIMEOUT_MS = 3000;
     private static final int SOCKET_TIMEOUT_MS = 100;
     private static final int NUM_TRIES_MULTIPATH_PREF_CHECK = 20;
     private static final long INTERVAL_MULTIPATH_PREF_CHECK_MS = 500;
@@ -1130,18 +1134,16 @@
 
         final ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
                 mCm, ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
-        mContext.registerReceiver(receiver, filter);
+        final int flags = SdkLevel.isAtLeastT() ? RECEIVER_EXPORTED : 0;
+        mContext.registerReceiver(receiver, filter, flags);
 
         // Create a broadcast PendingIntent for NETWORK_CALLBACK_ACTION.
         final Intent intent = new Intent(NETWORK_CALLBACK_ACTION)
                 .setPackage(mContext.getPackageName());
         // While ConnectivityService would put extra info such as network or request id before
         // broadcasting the inner intent. The MUTABLE flag needs to be added accordingly.
-        // TODO: replace with PendingIntent.FLAG_MUTABLE when this code compiles against S+ or
-        //  shims.
-        final int pendingIntentFlagMutable = 1 << 25;
         final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0 /*requestCode*/,
-                intent, PendingIntent.FLAG_CANCEL_CURRENT | pendingIntentFlagMutable);
+                intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
 
         // We will register for a WIFI network being available or lost.
         mCm.registerNetworkCallback(makeWifiNetworkRequest(), pendingIntent);
@@ -1181,14 +1183,13 @@
         // Avoid receiving broadcasts from other runs by appending a timestamp
         final String broadcastAction = NETWORK_CALLBACK_ACTION + System.currentTimeMillis();
         try {
-            // TODO: replace with PendingIntent.FLAG_MUTABLE when this code compiles against S+
             // Intent is mutable to receive EXTRA_NETWORK_REQUEST from ConnectivityService
-            final int pendingIntentFlagMutable = 1 << 25;
             final String extraBoolKey = "extra_bool";
             firstIntent = PendingIntent.getBroadcast(mContext,
                     0 /* requestCode */,
-                    new Intent(broadcastAction).putExtra(extraBoolKey, false),
-                    PendingIntent.FLAG_UPDATE_CURRENT | pendingIntentFlagMutable);
+                    new Intent(broadcastAction).putExtra(extraBoolKey, false)
+                            .setPackage(mContext.getPackageName()),
+                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
 
             if (useListen) {
                 mCm.registerNetworkCallback(firstRequest, firstIntent);
@@ -1200,8 +1201,9 @@
             // intent will be updated with the new extras
             secondIntent = PendingIntent.getBroadcast(mContext,
                     0 /* requestCode */,
-                    new Intent(broadcastAction).putExtra(extraBoolKey, true),
-                    PendingIntent.FLAG_UPDATE_CURRENT | pendingIntentFlagMutable);
+                    new Intent(broadcastAction).putExtra(extraBoolKey, true)
+                            .setPackage(mContext.getPackageName()),
+                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
 
             // Because secondIntent.intentFilterEquals the first, the request should be replaced
             if (useListen) {
@@ -1224,7 +1226,8 @@
                     networkFuture.complete(intent.getParcelableExtra(EXTRA_NETWORK));
                 }
             };
-            mContext.registerReceiver(receiver, filter);
+            final int flags = SdkLevel.isAtLeastT() ? RECEIVER_EXPORTED : 0;
+            mContext.registerReceiver(receiver, filter, flags);
 
             final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
             try {
@@ -1654,7 +1657,8 @@
         final NetworkCapabilities nc = mCm.getNetworkCapabilities(network);
 
         // Get number of supported concurrent keepalives for testing network.
-        final int[] keepalivesPerTransport = KeepaliveUtils.getSupportedKeepalives(mContext);
+        final int[] keepalivesPerTransport = runAsShell(NETWORK_SETTINGS,
+                () -> mCm.getSupportedKeepalives());
         return KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(
                 keepalivesPerTransport, nc);
     }
@@ -1669,6 +1673,9 @@
      * keepalives is set to 0.
      */
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+    // getSupportedKeepalives is available in updatable ConnectivityManager (S+)
+    // Also, this feature is not mainlined before S, and it's fine to skip on R- devices.
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) @ConnectivityModuleTest
     @Test
     public void testKeepaliveWifiUnsupported() throws Exception {
         assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
@@ -1685,6 +1692,9 @@
     }
 
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+    // getSupportedKeepalives is available in updatable ConnectivityManager (S+)
+    // Also, this feature is not mainlined before S, and it's fine to skip on R- devices.
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) @ConnectivityModuleTest
     @Test
     @RequiresDevice // Keepalive is not supported on virtual hardware
     public void testCreateTcpKeepalive() throws Exception {
@@ -1893,6 +1903,9 @@
      */
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     @Test
+    // getSupportedKeepalives is available in updatable ConnectivityManager (S+)
+    // Also, this feature is not mainlined before S, and it's fine to skip on R- devices.
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) @ConnectivityModuleTest
     @RequiresDevice // Keepalive is not supported on virtual hardware
     public void testSocketKeepaliveLimitWifi() throws Exception {
         assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
@@ -1943,6 +1956,9 @@
      */
     @AppModeFull(reason = "Cannot request network in instant app mode")
     @Test
+    // getSupportedKeepalives is available in updatable ConnectivityManager (S+)
+    // Also, this feature is not mainlined before S, and it's fine to skip on R- devices.
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) @ConnectivityModuleTest
     @RequiresDevice // Keepalive is not supported on virtual hardware
     public void testSocketKeepaliveLimitTelephony() throws Exception {
         if (!mPackageManager.hasSystemFeature(FEATURE_TELEPHONY)) {
@@ -1989,6 +2005,9 @@
      */
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     @Test
+    // getSupportedKeepalives is available in updatable ConnectivityManager (S+)
+    // Also, this feature is not mainlined before S, and it's fine to skip on R- devices.
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) @ConnectivityModuleTest
     @RequiresDevice // Keepalive is not supported on virtual hardware
     public void testSocketKeepaliveUnprivileged() throws Exception {
         assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
@@ -2111,7 +2130,12 @@
     @AppModeFull(reason = "NETWORK_AIRPLANE_MODE permission can't be granted to instant apps")
     @Test
     public void testSetAirplaneMode() throws Exception{
-        final boolean supportWifi = mPackageManager.hasSystemFeature(FEATURE_WIFI);
+        // Starting from T, wifi supports airplane mode enhancement which may not disconnect wifi
+        // when airplane mode is on. The actual behavior that the device will have could only be
+        // checked with hidden wifi APIs(see Settings.Secure.WIFI_APM_STATE). Thus, stop verifying
+        // wifi on T+ device.
+        final boolean verifyWifi = mPackageManager.hasSystemFeature(FEATURE_WIFI)
+                && !SdkLevel.isAtLeastT();
         final boolean supportTelephony = mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
         // store the current state of airplane mode
         final boolean isAirplaneModeEnabled = isAirplaneModeEnabled();
@@ -2122,7 +2146,7 @@
         // Verify that networks are available as expected if wifi or cell is supported. Continue the
         // test if none of them are supported since test should still able to verify the permission
         // mechanism.
-        if (supportWifi) {
+        if (verifyWifi) {
             mCtsNetUtils.ensureWifiConnected();
             registerCallbackAndWaitForAvailable(makeWifiNetworkRequest(), wifiCb);
         }
@@ -2146,7 +2170,7 @@
             // Verify that the enabling airplane mode takes effect as expected to prevent flakiness
             // caused by fast airplane mode switches. Ensure network lost before turning off
             // airplane mode.
-            if (supportWifi) waitForLost(wifiCb);
+            if (verifyWifi) waitForLost(wifiCb);
             if (supportTelephony) waitForLost(telephonyCb);
 
             // Verify we can disable Airplane Mode with correct permission:
@@ -2155,7 +2179,7 @@
             // Verify that turning airplane mode off takes effect as expected.
             // connectToCell only registers a request, it cannot / does not need to be called twice
             mCtsNetUtils.ensureWifiConnected();
-            if (supportWifi) waitForAvailable(wifiCb);
+            if (verifyWifi) waitForAvailable(wifiCb);
             if (supportTelephony) waitForAvailable(telephonyCb);
         } finally {
             // Restore the previous state of airplane mode and permissions:
@@ -2175,15 +2199,12 @@
                 c -> c instanceof CallbackEntry.Available);
     }
 
-    private void waitForAvailable(
+    private void waitForTransport(
             @NonNull final TestableNetworkCallback cb, final int expectedTransport) {
-        cb.eventuallyExpect(
-                CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
-                entry -> {
-                    final NetworkCapabilities nc = mCm.getNetworkCapabilities(entry.getNetwork());
-                    return nc.hasTransport(expectedTransport);
-                }
-        );
+        cb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
+                NETWORK_CALLBACK_TIMEOUT_MS,
+                entry -> ((CallbackEntry.CapabilitiesChanged) entry).getCaps()
+                        .hasTransport(expectedTransport));
     }
 
     private void waitForAvailable(
@@ -2378,19 +2399,20 @@
     }
 
     private class DetailedBlockedStatusCallback extends TestableNetworkCallback {
-        public void expectAvailableCallbacks(Network network) {
+        public void expectAvailableCallbacksWithBlockedReasonNone(Network network) {
             super.expectAvailableCallbacks(network, false /* suspended */, true /* validated */,
                     BLOCKED_REASON_NONE, NETWORK_CALLBACK_TIMEOUT_MS);
         }
         public void eventuallyExpectBlockedStatusCallback(Network network, int blockedStatus) {
             super.eventuallyExpect(CallbackEntry.BLOCKED_STATUS_INT, NETWORK_CALLBACK_TIMEOUT_MS,
-                    (it) -> it.getNetwork().equals(network) && it.getBlocked() == blockedStatus);
+                    (it) -> it.getNetwork().equals(network) && it.getReason() == blockedStatus);
         }
         public void onBlockedStatusChanged(Network network, int blockedReasons) {
+            Log.v(TAG, "onBlockedStatusChanged " + network + " " + blockedReasons);
             getHistory().add(new CallbackEntry.BlockedStatusInt(network, blockedReasons));
         }
         private void assertNoBlockedStatusCallback() {
-            super.assertNoCallbackThat(NO_CALLBACK_TIMEOUT_MS,
+            super.assertNoCallback(NO_CALLBACK_TIMEOUT_MS,
                     c -> c instanceof CallbackEntry.BlockedStatus);
         }
     }
@@ -2407,7 +2429,21 @@
         }
     }
 
-    private void doTestBlockedStatusCallback() throws Exception {
+    @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+    @Test
+    public void testBlockedStatusCallback() throws Exception {
+        // Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
+        // shims, and @IgnoreUpTo does not check that.
+        assumeTrue(TestUtils.shouldTestSApis());
+        // The test will need a stable active network that is persistent during the test.
+        // Try to connect to a wifi network and wait for it becomes the default network before
+        // starting the test to prevent from sudden active network change caused by previous
+        // executed tests.
+        if (mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
+            final Network expectedDefaultNetwork = mCtsNetUtils.ensureWifiConnected();
+            mCtsNetUtils.expectNetworkIsSystemDefault(expectedDefaultNetwork);
+        }
+
         final DetailedBlockedStatusCallback myUidCallback = new DetailedBlockedStatusCallback();
         final DetailedBlockedStatusCallback otherUidCallback = new DetailedBlockedStatusCallback();
 
@@ -2416,35 +2452,40 @@
         final Handler handler = new Handler(Looper.getMainLooper());
 
         registerDefaultNetworkCallback(myUidCallback, handler);
-        registerDefaultNetworkCallbackForUid(otherUid, otherUidCallback, handler);
+        runWithShellPermissionIdentity(() -> registerDefaultNetworkCallbackForUid(
+                otherUid, otherUidCallback, handler), NETWORK_SETTINGS);
 
-        final Network defaultNetwork = mCm.getActiveNetwork();
+        final Network defaultNetwork = myUidCallback.expect(CallbackEntry.AVAILABLE).getNetwork();
         final List<DetailedBlockedStatusCallback> allCallbacks =
                 List.of(myUidCallback, otherUidCallback);
         for (DetailedBlockedStatusCallback callback : allCallbacks) {
-            callback.expectAvailableCallbacks(defaultNetwork);
+            callback.eventuallyExpectBlockedStatusCallback(defaultNetwork, BLOCKED_REASON_NONE);
         }
 
         final Range<Integer> myUidRange = new Range<>(myUid, myUid);
         final Range<Integer> otherUidRange = new Range<>(otherUid, otherUid);
 
-        setRequireVpnForUids(true, List.of(myUidRange));
+        runWithShellPermissionIdentity(() -> setRequireVpnForUids(
+                true, List.of(myUidRange)), NETWORK_SETTINGS);
         myUidCallback.eventuallyExpectBlockedStatusCallback(defaultNetwork,
                 BLOCKED_REASON_LOCKDOWN_VPN);
         otherUidCallback.assertNoBlockedStatusCallback();
 
-        setRequireVpnForUids(true, List.of(myUidRange, otherUidRange));
+        runWithShellPermissionIdentity(() -> setRequireVpnForUids(
+                true, List.of(myUidRange, otherUidRange)), NETWORK_SETTINGS);
         myUidCallback.assertNoBlockedStatusCallback();
         otherUidCallback.eventuallyExpectBlockedStatusCallback(defaultNetwork,
                 BLOCKED_REASON_LOCKDOWN_VPN);
 
         // setRequireVpnForUids does no deduplication or refcounting. Removing myUidRange does not
         // unblock myUid because it was added to the blocked ranges twice.
-        setRequireVpnForUids(false, List.of(myUidRange));
+        runWithShellPermissionIdentity(() ->
+                setRequireVpnForUids(false, List.of(myUidRange)), NETWORK_SETTINGS);
         myUidCallback.assertNoBlockedStatusCallback();
         otherUidCallback.assertNoBlockedStatusCallback();
 
-        setRequireVpnForUids(false, List.of(myUidRange, otherUidRange));
+        runWithShellPermissionIdentity(() -> setRequireVpnForUids(
+                false, List.of(myUidRange, otherUidRange)), NETWORK_SETTINGS);
         myUidCallback.eventuallyExpectBlockedStatusCallback(defaultNetwork, BLOCKED_REASON_NONE);
         otherUidCallback.eventuallyExpectBlockedStatusCallback(defaultNetwork, BLOCKED_REASON_NONE);
 
@@ -2453,11 +2494,14 @@
     }
 
     @Test
-    public void testBlockedStatusCallback() {
-        // Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
-        // shims, and @IgnoreUpTo does not check that.
-        assumeTrue(TestUtils.shouldTestSApis());
-        runWithShellPermissionIdentity(() -> doTestBlockedStatusCallback(), NETWORK_SETTINGS);
+    public void testSetVpnDefaultForUids() {
+        assumeTrue(TestUtils.shouldTestUApis());
+        final String session = UUID.randomUUID().toString();
+        assertThrows(NullPointerException.class, () -> mCm.setVpnDefaultForUids(session, null));
+        assertThrows(SecurityException.class,
+                () -> mCm.setVpnDefaultForUids(session, new ArraySet<>()));
+        // For testing the complete behavior of setVpnDefaultForUids(), please refer to
+        // HostsideVpnTests.
     }
 
     private void doTestLegacyLockdownEnabled() throws Exception {
@@ -2531,16 +2575,23 @@
                 tetherUtils.registerTetheringEventCallback();
         try {
             tetherEventCallback.assumeWifiTetheringSupported(mContext);
+            // To prevent WiFi-to-WiFi interruption while entering APM:
+            //  - If WiFi is retained while entering APM, hotspot will also remain enabled.
+            //  - If WiFi is off before APM or disabled while entering APM, hotspot will be
+            //    disabled.
+            //
+            // To ensure hotspot always be disabled after enabling APM, disable wifi before
+            // enabling the hotspot.
+            mCtsNetUtils.disableWifi();
 
-            final TestableNetworkCallback wifiCb = new TestableNetworkCallback();
-            mCtsNetUtils.ensureWifiConnected();
-            registerCallbackAndWaitForAvailable(makeWifiNetworkRequest(), wifiCb);
+            tetherUtils.startWifiTethering(tetherEventCallback);
             // Update setting to verify the behavior.
             setAirplaneMode(true);
-            // Verify wifi lost to make sure airplane mode takes effect. This could
+            // Verify softap lost to make sure airplane mode takes effect. This could
             // prevent the race condition between airplane mode enabled and the followed
             // up wifi tethering enabled.
-            waitForLost(wifiCb);
+            tetherEventCallback.expectNoTetheringActive();
+
             // start wifi tethering
             tetherUtils.startWifiTethering(tetherEventCallback);
 
@@ -2565,6 +2616,7 @@
             ConnectivitySettingsManager.setPrivateDnsMode(mContext, curPrivateDnsMode);
             tetherUtils.unregisterTetheringEventCallback(tetherEventCallback);
             tetherUtils.stopAllTethering();
+            mCtsNetUtils.ensureWifiConnected();
         }
     }
 
@@ -2649,7 +2701,8 @@
 
             // Validate that an unmetered network is used over other networks.
             waitForAvailable(defaultCallback, wifiNetwork);
-            waitForAvailable(systemDefaultCallback, wifiNetwork);
+            systemDefaultCallback.eventuallyExpect(CallbackEntry.AVAILABLE,
+                    NETWORK_CALLBACK_TIMEOUT_MS, cb -> wifiNetwork.equals(cb.getNetwork()));
 
             // Validate that when setting unmetered to metered, unmetered is lost and replaced by
             // the network with the TEST transport. Also wait for validation here, in case there
@@ -2661,11 +2714,14 @@
             // callbacks may be received. Eventually, metered Wi-Fi should be the final available
             // callback in any case therefore confirm its receipt before continuing to assure the
             // system is in the expected state.
-            waitForAvailable(systemDefaultCallback, TRANSPORT_WIFI);
+            waitForTransport(systemDefaultCallback, TRANSPORT_WIFI);
         }, /* cleanup */ () -> {
-            // Validate that removing the test network will fallback to the default network.
+                // Validate that removing the test network will fallback to the default network.
                 runWithShellPermissionIdentity(tnt::teardown);
-                defaultCallback.expect(CallbackEntry.LOST, tnt, NETWORK_CALLBACK_TIMEOUT_MS);
+                // The other callbacks (LP or NC changes) would receive before LOST callback. Use
+                // eventuallyExpect to check callback for avoiding test flake.
+                defaultCallback.eventuallyExpect(CallbackEntry.LOST, NETWORK_CALLBACK_TIMEOUT_MS,
+                        lost -> tnt.getNetwork().equals(lost.getNetwork()));
                 waitForAvailable(defaultCallback);
             }, /* cleanup */ () -> {
                 setWifiMeteredStatusAndWait(ssid, oldMeteredValue, false /* waitForValidation */);
@@ -2685,6 +2741,7 @@
         // Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
         // shims, and @IgnoreUpTo does not check that.
         assumeTrue(TestUtils.shouldTestSApis());
+        assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
 
         final TestNetworkTracker tnt = callWithShellPermissionIdentity(
                 () -> initTestNetwork(mContext, TEST_LINKADDR, NETWORK_CALLBACK_TIMEOUT_MS));
@@ -2698,7 +2755,8 @@
                     OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY);
             registerTestOemNetworkPreferenceCallbacks(defaultCallback, systemDefaultCallback);
             waitForAvailable(defaultCallback, tnt.getNetwork());
-            waitForAvailable(systemDefaultCallback, wifiNetwork);
+            systemDefaultCallback.eventuallyExpect(CallbackEntry.AVAILABLE,
+                    NETWORK_CALLBACK_TIMEOUT_MS, cb -> wifiNetwork.equals(cb.getNetwork()));
         }, /* cleanup */ () -> {
                 runWithShellPermissionIdentity(tnt::teardown);
                 defaultCallback.expect(CallbackEntry.LOST, tnt, NETWORK_CALLBACK_TIMEOUT_MS);
@@ -2922,13 +2980,13 @@
 
         allowBadWifi();
 
-        final Network cellNetwork = mCtsNetUtils.connectToCell();
-        final Network wifiNetwork = prepareValidatedNetwork();
-
-        registerDefaultNetworkCallback(defaultCb);
-        registerNetworkCallback(makeWifiNetworkRequest(), wifiCb);
-
         try {
+            final Network cellNetwork = mCtsNetUtils.connectToCell();
+            final Network wifiNetwork = prepareValidatedNetwork();
+
+            registerDefaultNetworkCallback(defaultCb);
+            registerNetworkCallback(makeWifiNetworkRequest(), wifiCb);
+
             // Verify wifi is the default network.
             defaultCb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
                     entry -> wifiNetwork.equals(entry.getNetwork()));
@@ -2958,7 +3016,7 @@
             defaultCb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
                     entry -> cellNetwork.equals(entry.getNetwork()));
             // The network should not validate again.
-            wifiCb.assertNoCallbackThat(NO_CALLBACK_TIMEOUT_MS, c -> isValidatedCaps(c));
+            wifiCb.assertNoCallback(NO_CALLBACK_TIMEOUT_MS, c -> isValidatedCaps(c));
         } finally {
             resetAvoidBadWifi(previousAvoidBadWifi);
             mHttpServer.stop();
@@ -3130,7 +3188,7 @@
      */
     private void assertNoCallbackExceptCapOrLpChange(
             @NonNull final TestableNetworkCallback cb) {
-        cb.assertNoCallbackThat(NO_CALLBACK_TIMEOUT_MS,
+        cb.assertNoCallback(NO_CALLBACK_TIMEOUT_MS,
                 c -> !(c instanceof CallbackEntry.CapabilitiesChanged
                         || c instanceof CallbackEntry.LinkPropertiesChanged));
     }
@@ -3351,7 +3409,7 @@
     }
 
     private void checkFirewallBlocking(final DatagramSocket srcSock, final DatagramSocket dstSock,
-            final boolean expectBlock) throws Exception {
+            final boolean expectBlock, final int chain) throws Exception {
         final Random random = new Random();
         final byte[] sendData = new byte[100];
         random.nextBytes(sendData);
@@ -3364,19 +3422,28 @@
             if (expectBlock) {
                 return;
             }
-            fail("Expect not to be blocked by firewall but sending packet was blocked");
-        }
-
-        if (expectBlock) {
-            fail("Expect to be blocked by firewall but sending packet was not blocked");
+            fail("Expect not to be blocked by firewall but sending packet was blocked:"
+                    + " chain=" + chain
+                    + " chainEnabled=" + mCm.getFirewallChainEnabled(chain)
+                    + " uidFirewallRule=" + mCm.getUidFirewallRule(chain, Process.myUid()));
         }
 
         dstSock.receive(pkt);
         assertArrayEquals(sendData, pkt.getData());
+
+        if (expectBlock) {
+            fail("Expect to be blocked by firewall but sending packet was not blocked:"
+                    + " chain=" + chain
+                    + " chainEnabled=" + mCm.getFirewallChainEnabled(chain)
+                    + " uidFirewallRule=" + mCm.getUidFirewallRule(chain, Process.myUid()));
+        }
     }
 
     private static final boolean EXPECT_PASS = false;
     private static final boolean EXPECT_BLOCK = true;
+
+    // ALLOWLIST means the firewall denies all by default, uids must be explicitly allowed
+    // DENYLIST means the firewall allows all by default, uids must be explicitly denyed
     private static final boolean ALLOWLIST = true;
     private static final boolean DENYLIST = false;
 
@@ -3388,34 +3455,40 @@
         runWithShellPermissionIdentity(() -> {
             // Firewall chain status will be restored after the test.
             final boolean wasChainEnabled = mCm.getFirewallChainEnabled(chain);
+            final int previousUidFirewallRule = mCm.getUidFirewallRule(chain, myUid);
             final DatagramSocket srcSock = new DatagramSocket();
             final DatagramSocket dstSock = new DatagramSocket();
             testAndCleanup(() -> {
                 if (wasChainEnabled) {
                     mCm.setFirewallChainEnabled(chain, false /* enable */);
                 }
+                if (previousUidFirewallRule == ruleToAddMatch) {
+                    mCm.setUidFirewallRule(chain, myUid, ruleToRemoveMatch);
+                }
                 dstSock.setSoTimeout(SOCKET_TIMEOUT_MS);
 
                 // Chain disabled, UID not on chain.
-                checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+                checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS, chain);
 
                 // Chain enabled, UID not on chain.
                 mCm.setFirewallChainEnabled(chain, true /* enable */);
                 assertTrue(mCm.getFirewallChainEnabled(chain));
-                checkFirewallBlocking(srcSock, dstSock, isAllowList ? EXPECT_BLOCK : EXPECT_PASS);
+                checkFirewallBlocking(
+                        srcSock, dstSock, isAllowList ? EXPECT_BLOCK : EXPECT_PASS, chain);
 
                 // Chain enabled, UID on chain.
                 mCm.setUidFirewallRule(chain, myUid, ruleToAddMatch);
-                checkFirewallBlocking(srcSock, dstSock, isAllowList ?  EXPECT_PASS : EXPECT_BLOCK);
+                checkFirewallBlocking(
+                        srcSock, dstSock, isAllowList ?  EXPECT_PASS : EXPECT_BLOCK, chain);
 
                 // Chain disabled, UID on chain.
                 mCm.setFirewallChainEnabled(chain, false /* enable */);
                 assertFalse(mCm.getFirewallChainEnabled(chain));
-                checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+                checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS, chain);
 
                 // Chain disabled, UID not on chain.
                 mCm.setUidFirewallRule(chain, myUid, ruleToRemoveMatch);
-                checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS);
+                checkFirewallBlocking(srcSock, dstSock, EXPECT_PASS, chain);
             }, /* cleanup */ () -> {
                     srcSock.close();
                     dstSock.close();
@@ -3423,8 +3496,9 @@
                     // Restore the global chain status
                     mCm.setFirewallChainEnabled(chain, wasChainEnabled);
                 }, /* cleanup */ () -> {
+                    // Restore the uid firewall rule status
                     try {
-                        mCm.setUidFirewallRule(chain, myUid, ruleToRemoveMatch);
+                        mCm.setUidFirewallRule(chain, myUid, previousUidFirewallRule);
                     } catch (IllegalStateException ignored) {
                         // Removing match causes an exception when the rule entry for the uid does
                         // not exist. But this is fine and can be ignored.
@@ -3435,20 +3509,149 @@
 
     @Test @IgnoreUpTo(SC_V2) @ConnectivityModuleTest
     @AppModeFull(reason = "Socket cannot bind in instant app mode")
-    public void testFirewallBlocking() {
-        // ALLOWLIST means the firewall denies all by default, uids must be explicitly allowed
+    public void testFirewallBlockingDozable() {
         doTestFirewallBlocking(FIREWALL_CHAIN_DOZABLE, ALLOWLIST);
-        doTestFirewallBlocking(FIREWALL_CHAIN_POWERSAVE, ALLOWLIST);
-        doTestFirewallBlocking(FIREWALL_CHAIN_RESTRICTED, ALLOWLIST);
-        doTestFirewallBlocking(FIREWALL_CHAIN_LOW_POWER_STANDBY, ALLOWLIST);
+    }
 
-        // DENYLIST means the firewall allows all by default, uids must be explicitly denyed
+    @Test @IgnoreUpTo(SC_V2) @ConnectivityModuleTest
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
+    public void testFirewallBlockingPowersave() {
+        doTestFirewallBlocking(FIREWALL_CHAIN_POWERSAVE, ALLOWLIST);
+    }
+
+    @Test @IgnoreUpTo(SC_V2) @ConnectivityModuleTest
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
+    public void testFirewallBlockingRestricted() {
+        doTestFirewallBlocking(FIREWALL_CHAIN_RESTRICTED, ALLOWLIST);
+    }
+
+    @Test @IgnoreUpTo(SC_V2) @ConnectivityModuleTest
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
+    public void testFirewallBlockingLowPowerStandby() {
+        doTestFirewallBlocking(FIREWALL_CHAIN_LOW_POWER_STANDBY, ALLOWLIST);
+    }
+
+    @Test @IgnoreUpTo(SC_V2) @ConnectivityModuleTest
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
+    public void testFirewallBlockingStandby() {
         doTestFirewallBlocking(FIREWALL_CHAIN_STANDBY, DENYLIST);
+    }
+
+    @Test @IgnoreUpTo(SC_V2) @ConnectivityModuleTest
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
+    public void testFirewallBlockingOemDeny1() {
         doTestFirewallBlocking(FIREWALL_CHAIN_OEM_DENY_1, DENYLIST);
+    }
+
+    @Test @IgnoreUpTo(SC_V2) @ConnectivityModuleTest
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
+    public void testFirewallBlockingOemDeny2() {
         doTestFirewallBlocking(FIREWALL_CHAIN_OEM_DENY_2, DENYLIST);
+    }
+
+    @Test @IgnoreUpTo(SC_V2) @ConnectivityModuleTest
+    @AppModeFull(reason = "Socket cannot bind in instant app mode")
+    public void testFirewallBlockingOemDeny3() {
         doTestFirewallBlocking(FIREWALL_CHAIN_OEM_DENY_3, DENYLIST);
     }
 
+    private void assertSocketOpen(final Socket socket) throws Exception {
+        mCtsNetUtils.testHttpRequest(socket);
+    }
+
+    private void assertSocketClosed(final Socket socket) throws Exception {
+        try {
+            mCtsNetUtils.testHttpRequest(socket);
+            fail("Socket is expected to be closed");
+        } catch (SocketException expected) {
+        }
+    }
+
+    private static final boolean EXPECT_OPEN = false;
+    private static final boolean EXPECT_CLOSE = true;
+
+    private void doTestFirewallCloseSocket(final int chain, final int rule, final int targetUid,
+            final boolean expectClose) {
+        runWithShellPermissionIdentity(() -> {
+            // Firewall chain status will be restored after the test.
+            final boolean wasChainEnabled = mCm.getFirewallChainEnabled(chain);
+            final int previousUidFirewallRule = mCm.getUidFirewallRule(chain, targetUid);
+            final Socket socket = new Socket(TEST_HOST, HTTP_PORT);
+            socket.setSoTimeout(NETWORK_REQUEST_TIMEOUT_MS);
+            testAndCleanup(() -> {
+                mCm.setFirewallChainEnabled(chain, false /* enable */);
+                assertSocketOpen(socket);
+
+                try {
+                    mCm.setUidFirewallRule(chain, targetUid, rule);
+                } catch (IllegalStateException ignored) {
+                    // Removing match causes an exception when the rule entry for the uid does
+                    // not exist. But this is fine and can be ignored.
+                }
+                mCm.setFirewallChainEnabled(chain, true /* enable */);
+
+                if (expectClose) {
+                    assertSocketClosed(socket);
+                } else {
+                    assertSocketOpen(socket);
+                }
+            }, /* cleanup */ () -> {
+                    // Restore the global chain status
+                    mCm.setFirewallChainEnabled(chain, wasChainEnabled);
+                }, /* cleanup */ () -> {
+                    // Restore the uid firewall rule status
+                    try {
+                        mCm.setUidFirewallRule(chain, targetUid, previousUidFirewallRule);
+                    } catch (IllegalStateException ignored) {
+                        // Removing match causes an exception when the rule entry for the uid does
+                        // not exist. But this is fine and can be ignored.
+                    }
+                }, /* cleanup */ () -> {
+                    socket.close();
+                });
+        }, NETWORK_SETTINGS);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) @ConnectivityModuleTest
+    public void testFirewallCloseSocketAllowlistChainAllow() {
+        doTestFirewallCloseSocket(FIREWALL_CHAIN_DOZABLE, FIREWALL_RULE_ALLOW,
+                Process.myUid(), EXPECT_OPEN);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) @ConnectivityModuleTest
+    public void testFirewallCloseSocketAllowlistChainDeny() {
+        doTestFirewallCloseSocket(FIREWALL_CHAIN_DOZABLE, FIREWALL_RULE_DENY,
+                Process.myUid(), EXPECT_CLOSE);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) @ConnectivityModuleTest
+    public void testFirewallCloseSocketAllowlistChainOtherUid() {
+        doTestFirewallCloseSocket(FIREWALL_CHAIN_DOZABLE, FIREWALL_RULE_ALLOW,
+                Process.myUid() + 1, EXPECT_CLOSE);
+        doTestFirewallCloseSocket(FIREWALL_CHAIN_DOZABLE, FIREWALL_RULE_DENY,
+                Process.myUid() + 1, EXPECT_CLOSE);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) @ConnectivityModuleTest
+    public void testFirewallCloseSocketDenylistChainAllow() {
+        doTestFirewallCloseSocket(FIREWALL_CHAIN_STANDBY, FIREWALL_RULE_ALLOW,
+                Process.myUid(), EXPECT_OPEN);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) @ConnectivityModuleTest
+    public void testFirewallCloseSocketDenylistChainDeny() {
+        doTestFirewallCloseSocket(FIREWALL_CHAIN_STANDBY, FIREWALL_RULE_DENY,
+                Process.myUid(), EXPECT_CLOSE);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) @ConnectivityModuleTest
+    public void testFirewallCloseSocketDenylistChainOtherUid() {
+        doTestFirewallCloseSocket(FIREWALL_CHAIN_STANDBY, FIREWALL_RULE_ALLOW,
+                Process.myUid() + 1, EXPECT_OPEN);
+        doTestFirewallCloseSocket(FIREWALL_CHAIN_STANDBY, FIREWALL_RULE_DENY,
+                Process.myUid() + 1, EXPECT_OPEN);
+    }
+
     private void assumeTestSApis() {
         // Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
         // shims, and @IgnoreUpTo does not check that.
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 7e91478..732a42b 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -57,8 +57,8 @@
 import android.os.Handler
 import android.os.Looper
 import android.os.OutcomeReceiver
-import android.os.SystemProperties
 import android.os.Process
+import android.os.SystemProperties
 import android.platform.test.annotations.AppModeFull
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.net.module.util.ArrayTrackRecord
@@ -77,16 +77,10 @@
 import com.android.testutils.assertThrows
 import com.android.testutils.runAsShell
 import com.android.testutils.waitForIdle
-import org.junit.After
-import org.junit.Assume.assumeFalse
-import org.junit.Assume.assumeTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
 import java.io.IOException
 import java.net.Inet6Address
-import java.util.Random
 import java.net.Socket
+import java.util.Random
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.ExecutionException
 import java.util.concurrent.TimeUnit
@@ -99,6 +93,12 @@
 import kotlin.test.assertNull
 import kotlin.test.assertTrue
 import kotlin.test.fail
+import org.junit.After
+import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
 
 private const val TAG = "EthernetManagerTest"
 // This timeout does not affect the test duration for passing tests. It needs to be long enough to
@@ -392,7 +392,15 @@
     }
 
     // Setting the carrier up / down relies on TUNSETCARRIER which was added in kernel version 5.0.
-    private fun assumeChangingCarrierSupported() = assumeTrue(isKernelVersionAtLeast("5.0.0"))
+    private fun assumeChangingCarrierSupported() {
+        assumeTrue(isKernelVersionAtLeast("5.0.0"))
+    }
+
+    // Configuring a tap interface without carrier relies on IFF_NO_CARRIER
+    // which was added in kernel version 6.0.
+    private fun assumeCreateInterfaceWithoutCarrierSupported() {
+        assumeTrue(isKernelVersionAtLeast("6.0.0"))
+    }
 
     private fun isAdbOverEthernet(): Boolean {
         // If no ethernet interface is available, adb is not connected over ethernet.
@@ -417,7 +425,7 @@
     }
 
     // WARNING: setting hasCarrier to false requires kernel support. Call
-    // assumeChangingCarrierSupported() at the top of your test.
+    // assumeCreateInterfaceWithoutCarrierSupported() at the top of your test.
     private fun createInterface(hasCarrier: Boolean = true): EthernetTestInterface {
         val iface = EthernetTestInterface(
             context,
@@ -530,12 +538,10 @@
         eventuallyExpect(Lost::class) { n?.equals(it.network) ?: true }
 
     private fun TestableNetworkCallback.assertNeverLost(n: Network? = null) =
-        assertNoCallbackThat() {
-            it is Lost && (n?.equals(it.network) ?: true)
-        }
+        assertNoCallback { it is Lost && (n?.equals(it.network) ?: true) }
 
     private fun TestableNetworkCallback.assertNeverAvailable(n: Network? = null) =
-        assertNoCallbackThat { it is Available && (n?.equals(it.network) ?: true) }
+        assertNoCallback { it is Available && (n?.equals(it.network) ?: true) }
 
     private fun TestableNetworkCallback.expectCapabilitiesWithInterfaceName(name: String) =
         expect<CapabilitiesChanged> { it.caps.networkSpecifier == EthernetNetworkSpecifier(name) }
@@ -646,10 +652,9 @@
 
         val listener = EthernetStateListener()
         addInterfaceStateListener(listener)
-        // TODO(b/236895792): THIS IS A BUG! Existing server mode interfaces are not reported when
-        // an InterfaceStateListener is registered.
         // Note: using eventuallyExpect as there may be other interfaces present.
-        // listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_SERVER)
+        listener.eventuallyExpect(InterfaceStateChanged(iface.name,
+                STATE_LINK_UP, ROLE_SERVER, /* IpConfiguration */ null))
 
         releaseTetheredInterface()
         listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
@@ -794,15 +799,13 @@
 
     @Test
     fun testNetworkRequest_forInterfaceWhileTogglingCarrier() {
+        assumeCreateInterfaceWithoutCarrierSupported()
         assumeChangingCarrierSupported()
 
         val iface = createInterface(false /* hasCarrier */)
 
         val cb = requestNetwork(ETH_REQUEST)
-        // TUNSETCARRIER races with the bring up code, so the network *can* become available despite
-        // it being "created with no carrier".
-        // TODO(b/249611919): re-enable assertion once kernel supports IFF_NO_CARRIER.
-        // cb.assertNeverAvailable()
+        cb.assertNeverAvailable()
 
         iface.setCarrierEnabled(true)
         cb.expect<Available>()
diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
index ac50740..805dd65 100644
--- a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
+++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
@@ -60,7 +60,11 @@
 
 import com.android.internal.util.HexDump;
 import com.android.networkstack.apishim.ConstantsShim;
+import com.android.networkstack.apishim.Ikev2VpnProfileBuilderShimImpl;
+import com.android.networkstack.apishim.Ikev2VpnProfileShimImpl;
 import com.android.networkstack.apishim.VpnManagerShimImpl;
+import com.android.networkstack.apishim.common.Ikev2VpnProfileBuilderShim;
+import com.android.networkstack.apishim.common.Ikev2VpnProfileShim;
 import com.android.networkstack.apishim.common.VpnManagerShim;
 import com.android.networkstack.apishim.common.VpnProfileStateShim;
 import com.android.testutils.DevSdkIgnoreRule;
@@ -223,17 +227,28 @@
     }
 
     private Ikev2VpnProfile buildIkev2VpnProfileCommon(
-            @NonNull Ikev2VpnProfile.Builder builder, boolean isRestrictedToTestNetworks,
-            boolean requiresValidation) throws Exception {
+            @NonNull Ikev2VpnProfileBuilderShim builderShim, boolean isRestrictedToTestNetworks,
+            boolean requiresValidation, boolean automaticIpVersionSelectionEnabled,
+            boolean automaticNattKeepaliveTimerEnabled) throws Exception {
 
-        builder.setBypassable(true)
+        builderShim.setBypassable(true)
                 .setAllowedAlgorithms(TEST_ALLOWED_ALGORITHMS)
                 .setProxy(TEST_PROXY_INFO)
                 .setMaxMtu(TEST_MTU)
                 .setMetered(false);
         if (TestUtils.shouldTestTApis()) {
-            builder.setRequiresInternetValidation(requiresValidation);
+            builderShim.setRequiresInternetValidation(requiresValidation);
         }
+
+        if (TestUtils.shouldTestUApis()) {
+            builderShim.setAutomaticIpVersionSelectionEnabled(automaticIpVersionSelectionEnabled);
+            builderShim.setAutomaticNattKeepaliveTimerEnabled(automaticNattKeepaliveTimerEnabled);
+        }
+
+        // Convert shim back to Ikev2VpnProfile.Builder since restrictToTestNetworks is a hidden
+        // method and is not defined in shims.
+        // TODO: replace it in alternative way to remove the hidden method usage
+        final Ikev2VpnProfile.Builder builder = (Ikev2VpnProfile.Builder) builderShim.getBuilder();
         if (isRestrictedToTestNetworks) {
             builder.restrictToTestNetworks();
         }
@@ -249,13 +264,16 @@
                         ? IkeSessionTestUtils.IKE_PARAMS_V6 : IkeSessionTestUtils.IKE_PARAMS_V4,
                         IkeSessionTestUtils.CHILD_PARAMS);
 
-        final Ikev2VpnProfile.Builder builder =
-                new Ikev2VpnProfile.Builder(params)
+        final Ikev2VpnProfileBuilderShim builderShim =
+                Ikev2VpnProfileBuilderShimImpl.newInstance(params)
                         .setRequiresInternetValidation(requiresValidation)
                         .setProxy(TEST_PROXY_INFO)
                         .setMaxMtu(TEST_MTU)
                         .setMetered(false);
-
+        // Convert shim back to Ikev2VpnProfile.Builder since restrictToTestNetworks is a hidden
+        // method and is not defined in shims.
+        // TODO: replace it in alternative way to remove the hidden method usage
+        final Ikev2VpnProfile.Builder builder = (Ikev2VpnProfile.Builder) builderShim.getBuilder();
         if (isRestrictedToTestNetworks) {
             builder.restrictToTestNetworks();
         }
@@ -263,31 +281,35 @@
     }
 
     private Ikev2VpnProfile buildIkev2VpnProfilePsk(@NonNull String remote,
-            boolean isRestrictedToTestNetworks, boolean requiresValidation) throws Exception {
-        final Ikev2VpnProfile.Builder builder =
-                new Ikev2VpnProfile.Builder(remote, TEST_IDENTITY).setAuthPsk(TEST_PSK);
+            boolean isRestrictedToTestNetworks, boolean requiresValidation)
+            throws Exception {
+        final Ikev2VpnProfileBuilderShim builder =
+                Ikev2VpnProfileBuilderShimImpl.newInstance(remote, TEST_IDENTITY)
+                        .setAuthPsk(TEST_PSK);
         return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
-                requiresValidation);
+                requiresValidation, false /* automaticIpVersionSelectionEnabled */,
+                false /* automaticNattKeepaliveTimerEnabled */);
     }
 
     private Ikev2VpnProfile buildIkev2VpnProfileUsernamePassword(boolean isRestrictedToTestNetworks)
             throws Exception {
-
-        final Ikev2VpnProfile.Builder builder =
-                new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
+        final Ikev2VpnProfileBuilderShim builder =
+                Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
                         .setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa);
         return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
-                false /* requiresValidation */);
+                false /* requiresValidation */, false /* automaticIpVersionSelectionEnabled */,
+                false /* automaticNattKeepaliveTimerEnabled */);
     }
 
     private Ikev2VpnProfile buildIkev2VpnProfileDigitalSignature(boolean isRestrictedToTestNetworks)
             throws Exception {
-        final Ikev2VpnProfile.Builder builder =
-                new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
+        final Ikev2VpnProfileBuilderShim builder =
+                Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
                         .setAuthDigitalSignature(
                                 mUserCertKey.cert, mUserCertKey.key, mServerRootCa);
         return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks,
-                false /* requiresValidation */);
+                false /* requiresValidation */, false /* automaticIpVersionSelectionEnabled */,
+                false /* automaticNattKeepaliveTimerEnabled */);
     }
 
     private void checkBasicIkev2VpnProfile(@NonNull Ikev2VpnProfile profile) throws Exception {
@@ -529,11 +551,10 @@
             assertFalse(profileState.isLockdownEnabled());
         }
 
-        cb.expectCapabilitiesThat(vpnNetwork, TIMEOUT_MS,
-                caps -> caps.hasTransport(TRANSPORT_VPN)
-                && caps.hasCapability(NET_CAPABILITY_INTERNET)
-                && !caps.hasCapability(NET_CAPABILITY_VALIDATED)
-                && Process.myUid() == caps.getOwnerUid());
+        cb.expectCaps(vpnNetwork, TIMEOUT_MS, c -> c.hasTransport(TRANSPORT_VPN)
+                && c.hasCapability(NET_CAPABILITY_INTERNET)
+                && !c.hasCapability(NET_CAPABILITY_VALIDATED)
+                && Process.myUid() == c.getOwnerUid());
         cb.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, vpnNetwork);
         cb.expect(CallbackEntry.BLOCKED_STATUS, vpnNetwork);
 
@@ -688,6 +709,56 @@
                 true /* testSessionKey */, false /* testIkeTunConnParams */);
     }
 
+    @Test
+    public void testBuildIkev2VpnProfileWithAutomaticNattKeepaliveTimerEnabled() throws Exception {
+        // Cannot use @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) because this test also requires API
+        // 34 shims, and @IgnoreUpTo does not check that.
+        assumeTrue(TestUtils.shouldTestUApis());
+
+        final Ikev2VpnProfile profileWithDefaultValue = buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6,
+                false /* isRestrictedToTestNetworks */, false /* requiresValidation */);
+        final Ikev2VpnProfileShim<Ikev2VpnProfile> shimWithDefaultValue =
+                Ikev2VpnProfileShimImpl.newInstance(profileWithDefaultValue);
+        assertFalse(shimWithDefaultValue.isAutomaticNattKeepaliveTimerEnabled());
+
+        final Ikev2VpnProfileBuilderShim builder =
+                Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
+                        .setAuthPsk(TEST_PSK);
+        final Ikev2VpnProfile profile = buildIkev2VpnProfileCommon(builder,
+                false /* isRestrictedToTestNetworks */,
+                false /* requiresValidation */,
+                false /* automaticIpVersionSelectionEnabled */,
+                true /* automaticNattKeepaliveTimerEnabled */);
+        final Ikev2VpnProfileShim<Ikev2VpnProfile> shim =
+                Ikev2VpnProfileShimImpl.newInstance(profile);
+        assertTrue(shim.isAutomaticNattKeepaliveTimerEnabled());
+    }
+
+    @Test
+    public void testBuildIkev2VpnProfileWithAutomaticIpVersionSelectionEnabled() throws Exception {
+        // Cannot use @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) because this test also requires API
+        // 34 shims, and @IgnoreUpTo does not check that.
+        assumeTrue(TestUtils.shouldTestUApis());
+
+        final Ikev2VpnProfile profileWithDefaultValue = buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6,
+                false /* isRestrictedToTestNetworks */, false /* requiresValidation */);
+        final Ikev2VpnProfileShim<Ikev2VpnProfile> shimWithDefaultValue =
+                Ikev2VpnProfileShimImpl.newInstance(profileWithDefaultValue);
+        assertFalse(shimWithDefaultValue.isAutomaticIpVersionSelectionEnabled());
+
+        final Ikev2VpnProfileBuilderShim builder =
+                Ikev2VpnProfileBuilderShimImpl.newInstance(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
+                        .setAuthPsk(TEST_PSK);
+        final Ikev2VpnProfile profile = buildIkev2VpnProfileCommon(builder,
+                false /* isRestrictedToTestNetworks */,
+                false /* requiresValidation */,
+                true /* automaticIpVersionSelectionEnabled */,
+                false /* automaticNattKeepaliveTimerEnabled */);
+        final Ikev2VpnProfileShim<Ikev2VpnProfile> shim =
+                Ikev2VpnProfileShimImpl.newInstance(profile);
+        assertTrue(shim.isAutomaticIpVersionSelectionEnabled());
+    }
+
     private static class CertificateAndKey {
         public final X509Certificate cert;
         public final PrivateKey key;
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index 8234ec1..cc0a5df 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -16,6 +16,7 @@
 
 package android.net.cts;
 
+import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.net.IpSecAlgorithm.AUTH_AES_CMAC;
 import static android.net.IpSecAlgorithm.AUTH_AES_XCBC;
 import static android.net.IpSecAlgorithm.AUTH_CRYPT_AES_GCM;
@@ -52,7 +53,9 @@
 
 import static com.android.compatibility.common.util.PropertyUtil.getFirstApiLevel;
 import static com.android.compatibility.common.util.PropertyUtil.getVendorApiLevel;
+import static com.android.testutils.DeviceInfoUtils.isKernelVersionAtLeast;
 import static com.android.testutils.MiscAsserts.assertThrows;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -62,6 +65,8 @@
 
 import android.net.IpSecAlgorithm;
 import android.net.IpSecManager;
+import android.net.IpSecManager.SecurityParameterIndex;
+import android.net.IpSecManager.UdpEncapsulationSocket;
 import android.net.IpSecTransform;
 import android.net.TrafficStats;
 import android.os.Build;
@@ -73,6 +78,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.modules.utils.build.SdkLevel;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 
@@ -120,7 +126,7 @@
     @Test
     public void testAllocSpi() throws Exception {
         for (InetAddress addr : GOOGLE_DNS_LIST) {
-            IpSecManager.SecurityParameterIndex randomSpi = null, droidSpi = null;
+            SecurityParameterIndex randomSpi, droidSpi;
             randomSpi = mISM.allocateSecurityParameterIndex(addr);
             assertTrue(
                     "Failed to receive a valid SPI",
@@ -258,6 +264,24 @@
         accepted.close();
     }
 
+    private IpSecTransform buildTransportModeTransform(
+            SecurityParameterIndex spi, InetAddress localAddr,
+            UdpEncapsulationSocket encapSocket)
+            throws Exception {
+        final IpSecTransform.Builder builder =
+                new IpSecTransform.Builder(InstrumentationRegistry.getContext())
+                        .setEncryption(new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
+                        .setAuthentication(
+                                new IpSecAlgorithm(
+                                        IpSecAlgorithm.AUTH_HMAC_SHA256,
+                                        AUTH_KEY,
+                                        AUTH_KEY.length * 8));
+        if (encapSocket != null) {
+            builder.setIpv4Encapsulation(encapSocket, encapSocket.getPort());
+        }
+        return builder.buildTransportModeTransform(localAddr, spi);
+    }
+
     /*
      * Alloc outbound SPI
      * Alloc inbound SPI
@@ -268,21 +292,8 @@
      * release transform
      * send data (expect exception)
      */
-    @Test
-    public void testCreateTransform() throws Exception {
-        InetAddress localAddr = InetAddress.getByName(IPV4_LOOPBACK);
-        IpSecManager.SecurityParameterIndex spi =
-                mISM.allocateSecurityParameterIndex(localAddr);
-
-        IpSecTransform transform =
-                new IpSecTransform.Builder(InstrumentationRegistry.getContext())
-                        .setEncryption(new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
-                        .setAuthentication(
-                                new IpSecAlgorithm(
-                                        IpSecAlgorithm.AUTH_HMAC_SHA256,
-                                        AUTH_KEY,
-                                        AUTH_KEY.length * 8))
-                        .buildTransportModeTransform(localAddr, spi);
+    private void doTestCreateTransform(String loopbackAddrString, boolean encap) throws Exception {
+        InetAddress localAddr = InetAddress.getByName(loopbackAddrString);
 
         final boolean [][] applyInApplyOut = {
                 {false, false}, {false, true}, {true, false}, {true,true}};
@@ -291,57 +302,110 @@
 
         byte[] in = new byte[data.length];
         DatagramPacket inPacket = new DatagramPacket(in, in.length);
-        DatagramSocket localSocket;
         int localPort;
 
         for(boolean[] io : applyInApplyOut) {
             boolean applyIn = io[0];
             boolean applyOut = io[1];
-            // Bind localSocket to a random available port.
-            localSocket = new DatagramSocket(0);
-            localPort = localSocket.getLocalPort();
-            localSocket.setSoTimeout(200);
-            outPacket.setPort(localPort);
-            if (applyIn) {
-                mISM.applyTransportModeTransform(
-                        localSocket, IpSecManager.DIRECTION_IN, transform);
-            }
-            if (applyOut) {
-                mISM.applyTransportModeTransform(
-                        localSocket, IpSecManager.DIRECTION_OUT, transform);
-            }
-            if (applyIn == applyOut) {
-                localSocket.send(outPacket);
-                localSocket.receive(inPacket);
-                assertTrue("Encapsulated data did not match.",
-                        Arrays.equals(outPacket.getData(), inPacket.getData()));
-                mISM.removeTransportModeTransforms(localSocket);
-                localSocket.close();
-            } else {
-                try {
+            try (
+                SecurityParameterIndex spi = mISM.allocateSecurityParameterIndex(localAddr);
+                UdpEncapsulationSocket encapSocket = encap
+                        ? getPrivilegedUdpEncapSocket(/*ipv6=*/ localAddr instanceof Inet6Address)
+                        : null;
+                IpSecTransform transform = buildTransportModeTransform(spi, localAddr,
+                        encapSocket);
+                // Bind localSocket to a random available port.
+                DatagramSocket localSocket = new DatagramSocket(0);
+            ) {
+                localPort = localSocket.getLocalPort();
+                localSocket.setSoTimeout(200);
+                outPacket.setPort(localPort);
+                if (applyIn) {
+                    mISM.applyTransportModeTransform(
+                            localSocket, IpSecManager.DIRECTION_IN, transform);
+                }
+                if (applyOut) {
+                    mISM.applyTransportModeTransform(
+                            localSocket, IpSecManager.DIRECTION_OUT, transform);
+                }
+                if (applyIn == applyOut) {
                     localSocket.send(outPacket);
                     localSocket.receive(inPacket);
-                } catch (IOException e) {
-                    continue;
-                } finally {
+                    assertTrue("Encrypted data did not match.",
+                            Arrays.equals(outPacket.getData(), inPacket.getData()));
                     mISM.removeTransportModeTransforms(localSocket);
-                    localSocket.close();
+                } else {
+                    try {
+                        localSocket.send(outPacket);
+                        localSocket.receive(inPacket);
+                    } catch (IOException e) {
+                        continue;
+                    } finally {
+                        mISM.removeTransportModeTransforms(localSocket);
+                    }
+                    // FIXME: This check is disabled because sockets currently receive data
+                    // if there is a valid SA for decryption, even when the input policy is
+                    // not applied to a socket.
+                    //  fail("Data IO should fail on asymmetrical transforms! + Input="
+                    //          + applyIn + " Output=" + applyOut);
                 }
-                // FIXME: This check is disabled because sockets currently receive data
-                // if there is a valid SA for decryption, even when the input policy is
-                // not applied to a socket.
-                //  fail("Data IO should fail on asymmetrical transforms! + Input="
-                //          + applyIn + " Output=" + applyOut);
             }
         }
-        transform.close();
+    }
+
+    private UdpEncapsulationSocket getPrivilegedUdpEncapSocket(boolean ipv6) throws Exception {
+        return runAsShell(NETWORK_SETTINGS, () -> {
+            if (ipv6) {
+                return mISM.openUdpEncapsulationSocket(65536);
+            } else {
+                // Can't pass 0 to IpSecManager#openUdpEncapsulationSocket(int).
+                return mISM.openUdpEncapsulationSocket();
+            }
+        });
+    }
+
+    private static boolean isIpv6UdpEncapSupportedByKernel() {
+        return isKernelVersionAtLeast("5.15.31")
+                || (isKernelVersionAtLeast("5.10.108") && !isKernelVersionAtLeast("5.15.0"));
+    }
+
+    // Packet private for use in IpSecManagerTunnelTest
+    static boolean isIpv6UdpEncapSupported() {
+        return SdkLevel.isAtLeastU() && isIpv6UdpEncapSupportedByKernel();
+    }
+
+    // Packet private for use in IpSecManagerTunnelTest
+    static void assumeExperimentalIpv6UdpEncapSupported() throws Exception {
+        assumeTrue("Not supported before U", SdkLevel.isAtLeastU());
+        assumeTrue("Not supported by kernel", isIpv6UdpEncapSupportedByKernel());
+    }
+
+    @Test
+    public void testCreateTransformIpv4() throws Exception {
+        doTestCreateTransform(IPV4_LOOPBACK, false);
+    }
+
+    @Test
+    public void testCreateTransformIpv6() throws Exception {
+        doTestCreateTransform(IPV6_LOOPBACK, false);
+    }
+
+    @Test
+    public void testCreateTransformIpv4Encap() throws Exception {
+        doTestCreateTransform(IPV4_LOOPBACK, true);
+    }
+
+    @Test
+    public void testCreateTransformIpv6Encap() throws Exception {
+        assumeExperimentalIpv6UdpEncapSupported();
+        doTestCreateTransform(IPV6_LOOPBACK, true);
     }
 
     /** Snapshot of TrafficStats as of initStatsChecker call for later comparisons */
     private static class StatsChecker {
         private static final double ERROR_MARGIN_BYTES = 1.05;
         private static final double ERROR_MARGIN_PKTS = 1.05;
-        private static final int MAX_WAIT_TIME_MILLIS = 1000;
+        private static final int MAX_WAIT_TIME_MILLIS = 3000;
 
         private static long uidTxBytes;
         private static long uidRxBytes;
@@ -393,9 +457,8 @@
             long newUidRxPackets = TrafficStats.getUidRxPackets(Os.getuid());
 
             assertEquals(expectedTxByteDelta, newUidTxBytes - uidTxBytes);
-            assertTrue(
-                    newUidRxBytes - uidRxBytes >= minRxByteDelta
-                            && newUidRxBytes - uidRxBytes <= maxRxByteDelta);
+            assertTrue("Not enough bytes", newUidRxBytes - uidRxBytes >= minRxByteDelta);
+            assertTrue("Too many bytes", newUidRxBytes - uidRxBytes <= maxRxByteDelta);
             assertEquals(expectedTxPacketDelta, newUidTxPackets - uidTxPackets);
             assertEquals(expectedRxPacketDelta, newUidRxPackets - uidRxPackets);
         }
@@ -503,8 +566,8 @@
         StatsChecker.initStatsChecker();
         InetAddress local = InetAddress.getByName(localAddress);
 
-        try (IpSecManager.UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket();
-                IpSecManager.SecurityParameterIndex spi =
+        try (UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket();
+                SecurityParameterIndex spi =
                         mISM.allocateSecurityParameterIndex(local)) {
 
             IpSecTransform.Builder transformBuilder =
@@ -656,7 +719,7 @@
     public void testIkeOverUdpEncapSocket() throws Exception {
         // IPv6 not supported for UDP-encap-ESP
         InetAddress local = InetAddress.getByName(IPV4_LOOPBACK);
-        try (IpSecManager.UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
+        try (UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
             NativeUdpSocket wrappedEncapSocket =
                     new NativeUdpSocket(encapSocket.getFileDescriptor());
             checkIkePacket(wrappedEncapSocket, local);
@@ -665,7 +728,7 @@
             IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
             IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
 
-            try (IpSecManager.SecurityParameterIndex spi =
+            try (SecurityParameterIndex spi =
                             mISM.allocateSecurityParameterIndex(local);
                     IpSecTransform transform =
                             new IpSecTransform.Builder(InstrumentationRegistry.getContext())
@@ -1498,7 +1561,7 @@
 
     @Test
     public void testOpenUdpEncapSocketSpecificPort() throws Exception {
-        IpSecManager.UdpEncapsulationSocket encapSocket = null;
+        UdpEncapsulationSocket encapSocket = null;
         int port = -1;
         for (int i = 0; i < MAX_PORT_BIND_ATTEMPTS; i++) {
             try {
@@ -1527,7 +1590,7 @@
 
     @Test
     public void testOpenUdpEncapSocketRandomPort() throws Exception {
-        try (IpSecManager.UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
+        try (UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
             assertTrue("Returned invalid port", encapSocket.getPort() != 0);
         }
     }
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
index a9a3380..1ede5c1 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
@@ -18,6 +18,8 @@
 
 import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS;
 import static android.net.IpSecManager.UdpEncapsulationSocket;
+import static android.net.cts.IpSecManagerTest.assumeExperimentalIpv6UdpEncapSupported;
+import static android.net.cts.IpSecManagerTest.isIpv6UdpEncapSupported;
 import static android.net.cts.PacketUtils.AES_CBC_BLK_SIZE;
 import static android.net.cts.PacketUtils.AES_CBC_IV_LEN;
 import static android.net.cts.PacketUtils.BytePayload;
@@ -32,10 +34,13 @@
 import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.AF_INET6;
 
+import static com.android.compatibility.common.util.PropertyUtil.getVsrApiLevel;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
@@ -73,6 +78,7 @@
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 
+// TODO: b/268552823 Improve the readability of IpSecManagerTunnelTest
 @RunWith(AndroidJUnit4.class)
 @AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps")
 public class IpSecManagerTunnelTest extends IpSecBaseTest {
@@ -107,6 +113,7 @@
 
     private static final int IP4_PREFIX_LEN = 32;
     private static final int IP6_PREFIX_LEN = 128;
+    private static final int IP6_UDP_ENCAP_SOCKET_PORT_ANY = 65536;
 
     private static final int TIMEOUT_MS = 500;
 
@@ -255,14 +262,23 @@
          *
          * @param ipsecNetwork The IPsec Interface based Network for binding sockets on
          * @param tunnelIface The IPsec tunnel interface that will be tested
-         * @param underlyingTunUtils The utility of the IPsec tunnel interface's underlying TUN
-         *     network
-         * @return the integer port of the inner socket if outbound, or 0 if inbound
-         *     IpSecTunnelTestRunnable
+         * @param tunUtils The utility of the IPsec tunnel interface's underlying TUN network
+         * @param inTunnelTransform The inbound tunnel mode transform
+         * @param outTunnelTransform The outbound tunnel mode transform
+         * @param localOuter The local address of the outer IP packet
+         * @param remoteOuter The remote address of the outer IP packet
+         * @param seqNum The expected sequence number of the inbound packet
          * @throws Exception if any part of the test failed.
          */
         public abstract int run(
-                Network ipsecNetwork, IpSecTunnelInterface tunnelIface, TunUtils underlyingTunUtils)
+                Network ipsecNetwork,
+                IpSecTunnelInterface tunnelIface,
+                TunUtils tunUtils,
+                IpSecTransform inTunnelTransform,
+                IpSecTransform outTunnelTransform,
+                InetAddress localOuter,
+                InetAddress remoteOuter,
+                int seqNum)
                 throws Exception;
     }
 
@@ -297,19 +313,41 @@
         return expectedPacketSize;
     }
 
+    private UdpEncapsulationSocket openUdpEncapsulationSocket(int ipVersion) throws Exception {
+        if (ipVersion == AF_INET) {
+            return mISM.openUdpEncapsulationSocket();
+        }
+
+        if (!isIpv6UdpEncapSupported()) {
+            throw new UnsupportedOperationException("IPv6 UDP encapsulation unsupported");
+        }
+
+        return mISM.openUdpEncapsulationSocket(IP6_UDP_ENCAP_SOCKET_PORT_ANY);
+    }
+
     private interface IpSecTunnelTestRunnableFactory {
+        /**
+         * Build a IpSecTunnelTestRunnable.
+         *
+         * @param transportInTunnelMode indicate if there needs to be a transport mode transform
+         *     inside the tunnel mode transform
+         * @param spi The IPsec SPI
+         * @param localInner The local address of the inner IP packet
+         * @param remoteInner The remote address of the inner IP packet
+         * @param inTransportTransform The inbound transport mode transform
+         * @param outTransportTransform The outbound transport mode transform
+         * @param encapSocket The UDP encapsulation socket or null
+         * @param innerSocketPort The inner socket port
+         */
         IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
                 boolean transportInTunnelMode,
                 int spi,
                 InetAddress localInner,
                 InetAddress remoteInner,
-                InetAddress localOuter,
-                InetAddress remoteOuter,
                 IpSecTransform inTransportTransform,
                 IpSecTransform outTransportTransform,
-                int encapPort,
-                int innerSocketPort,
-                int expectedPacketSize)
+                UdpEncapsulationSocket encapSocket,
+                int innerSocketPort)
                 throws Exception;
     }
 
@@ -319,17 +357,21 @@
                 int spi,
                 InetAddress localInner,
                 InetAddress remoteInner,
-                InetAddress localOuter,
-                InetAddress remoteOuter,
                 IpSecTransform inTransportTransform,
                 IpSecTransform outTransportTransform,
-                int encapPort,
-                int unusedInnerSocketPort,
-                int expectedPacketSize) {
+                UdpEncapsulationSocket encapSocket,
+                int unusedInnerSocketPort) {
             return new IpSecTunnelTestRunnable() {
                 @Override
                 public int run(
-                        Network ipsecNetwork, IpSecTunnelInterface tunnelIface, TunUtils tunUtils)
+                        Network ipsecNetwork,
+                        IpSecTunnelInterface tunnelIface,
+                        TunUtils tunUtils,
+                        IpSecTransform inTunnelTransform,
+                        IpSecTransform outTunnelTransform,
+                        InetAddress localOuter,
+                        InetAddress remoteOuter,
+                        int seqNum)
                         throws Exception {
                     // Build a socket and send traffic
                     JavaUdpSocket socket = new JavaUdpSocket(localInner);
@@ -349,9 +391,14 @@
                     // Verify that an encrypted packet is sent. As of right now, checking encrypted
                     // body is not possible, due to the test not knowing some of the fields of the
                     // inner IP header (flow label, flags, etc)
+                    int innerFamily = localInner instanceof Inet4Address ? AF_INET : AF_INET6;
+                    int outerFamily = localOuter instanceof Inet4Address ? AF_INET : AF_INET6;
+                    boolean useEncap = encapSocket != null;
+                    int expectedPacketSize =
+                            getPacketSize(
+                                    innerFamily, outerFamily, useEncap, transportInTunnelMode);
                     tunUtils.awaitEspPacketNoPlaintext(
-                            spi, TEST_DATA, encapPort != 0, expectedPacketSize);
-
+                            spi, TEST_DATA, useEncap, expectedPacketSize);
                     socket.close();
 
                     return innerSocketPort;
@@ -367,18 +414,22 @@
                 int spi,
                 InetAddress localInner,
                 InetAddress remoteInner,
-                InetAddress localOuter,
-                InetAddress remoteOuter,
                 IpSecTransform inTransportTransform,
                 IpSecTransform outTransportTransform,
-                int encapPort,
-                int innerSocketPort,
-                int expectedPacketSize)
+                UdpEncapsulationSocket encapSocket,
+                int innerSocketPort)
                 throws Exception {
             return new IpSecTunnelTestRunnable() {
                 @Override
                 public int run(
-                        Network ipsecNetwork, IpSecTunnelInterface tunnelIface, TunUtils tunUtils)
+                        Network ipsecNetwork,
+                        IpSecTunnelInterface tunnelIface,
+                        TunUtils tunUtils,
+                        IpSecTransform inTunnelTransform,
+                        IpSecTransform outTunnelTransform,
+                        InetAddress localOuter,
+                        InetAddress remoteOuter,
+                        int seqNum)
                         throws Exception {
                     // Build a socket and receive traffic
                     JavaUdpSocket socket = new JavaUdpSocket(localInner, innerSocketPort);
@@ -412,18 +463,22 @@
                 int spi,
                 InetAddress localInner,
                 InetAddress remoteInner,
-                InetAddress localOuter,
-                InetAddress remoteOuter,
                 IpSecTransform inTransportTransform,
                 IpSecTransform outTransportTransform,
-                int encapPort,
-                int innerSocketPort,
-                int expectedPacketSize)
+                UdpEncapsulationSocket encapSocket,
+                int innerSocketPort)
                 throws Exception {
             return new IpSecTunnelTestRunnable() {
                 @Override
                 public int run(
-                        Network ipsecNetwork, IpSecTunnelInterface tunnelIface, TunUtils tunUtils)
+                        Network ipsecNetwork,
+                        IpSecTunnelInterface tunnelIface,
+                        TunUtils tunUtils,
+                        IpSecTransform inTunnelTransform,
+                        IpSecTransform outTunnelTransform,
+                        InetAddress localOuter,
+                        InetAddress remoteOuter,
+                        int seqNum)
                         throws Exception {
                     // Build a socket and receive traffic
                     JavaUdpSocket socket = new JavaUdpSocket(localInner);
@@ -448,7 +503,8 @@
                                         remoteOuter,
                                         localOuter,
                                         socket.getPort(),
-                                        encapPort);
+                                        encapSocket != null ? encapSocket.getPort() : 0,
+                                        seqNum);
                     } else {
                         pkt =
                                 getTunnelModePacket(
@@ -458,7 +514,8 @@
                                         remoteOuter,
                                         localOuter,
                                         socket.getPort(),
-                                        encapPort);
+                                        encapSocket != null ? encapSocket.getPort() : 0,
+                                        seqNum);
                     }
                     tunUtils.injectPacket(pkt);
 
@@ -475,13 +532,16 @@
 
     private class MigrateIpSecTunnelTestRunnableFactory implements IpSecTunnelTestRunnableFactory {
         private final IpSecTunnelTestRunnableFactory mTestRunnableFactory;
+        private final boolean mTestEncapTypeChange;
 
-        MigrateIpSecTunnelTestRunnableFactory(boolean isOutputTest) {
+        MigrateIpSecTunnelTestRunnableFactory(boolean isOutputTest, boolean testEncapTypeChange) {
             if (isOutputTest) {
                 mTestRunnableFactory = new OutputIpSecTunnelTestRunnableFactory();
             } else {
                 mTestRunnableFactory = new InputPacketGeneratorIpSecTunnelTestRunnableFactory();
             }
+
+            mTestEncapTypeChange = testEncapTypeChange;
         }
 
         @Override
@@ -490,17 +550,21 @@
                 int spi,
                 InetAddress localInner,
                 InetAddress remoteInner,
-                InetAddress localOuter,
-                InetAddress remoteOuter,
                 IpSecTransform inTransportTransform,
                 IpSecTransform outTransportTransform,
-                int encapPort,
-                int unusedInnerSocketPort,
-                int expectedPacketSize) {
+                UdpEncapsulationSocket encapSocket,
+                int unusedInnerSocketPort) {
             return new IpSecTunnelTestRunnable() {
                 @Override
                 public int run(
-                        Network ipsecNetwork, IpSecTunnelInterface tunnelIface, TunUtils tunUtils)
+                        Network ipsecNetwork,
+                        IpSecTunnelInterface tunnelIface,
+                        TunUtils tunUtils,
+                        IpSecTransform inTunnelTransform,
+                        IpSecTransform outTunnelTransform,
+                        InetAddress localOuter,
+                        InetAddress remoteOuter,
+                        int seqNum)
                         throws Exception {
                     mTestRunnableFactory
                             .getIpSecTunnelTestRunnable(
@@ -508,17 +572,25 @@
                                     spi,
                                     localInner,
                                     remoteInner,
-                                    localOuter,
-                                    remoteOuter,
                                     inTransportTransform,
                                     outTransportTransform,
-                                    encapPort,
-                                    unusedInnerSocketPort,
-                                    expectedPacketSize)
-                            .run(ipsecNetwork, tunnelIface, sTunWrapper.utils);
-
+                                    encapSocket,
+                                    unusedInnerSocketPort)
+                            .run(
+                                    ipsecNetwork,
+                                    tunnelIface,
+                                    tunUtils,
+                                    inTunnelTransform,
+                                    outTunnelTransform,
+                                    localOuter,
+                                    remoteOuter,
+                                    seqNum);
                     tunnelIface.setUnderlyingNetwork(sTunWrapperNew.network);
 
+                    final boolean useEncapBeforeMigrate = encapSocket != null;
+                    final boolean useEncapAfterMigrate =
+                            mTestEncapTypeChange ? !useEncapBeforeMigrate : useEncapBeforeMigrate;
+
                     // Verify migrating to IPv4 and IPv6 addresses. It ensures that not only
                     // can IPsec tunnel migrate across interfaces, IPsec tunnel can also migrate to
                     // a different address on the same interface.
@@ -527,21 +599,24 @@
                             remoteInner,
                             LOCAL_OUTER_4_NEW,
                             REMOTE_OUTER_4_NEW,
-                            encapPort != 0,
+                            useEncapAfterMigrate,
                             transportInTunnelMode,
                             sTunWrapperNew.utils,
                             tunnelIface,
                             ipsecNetwork);
-                    checkMigratedTunnel(
-                            localInner,
-                            remoteInner,
-                            LOCAL_OUTER_6_NEW,
-                            REMOTE_OUTER_6_NEW,
-                            false, // IPv6 does not support UDP encapsulation
-                            transportInTunnelMode,
-                            sTunWrapperNew.utils,
-                            tunnelIface,
-                            ipsecNetwork);
+
+                    if (!useEncapAfterMigrate || isIpv6UdpEncapSupported()) {
+                        checkMigratedTunnel(
+                                localInner,
+                                remoteInner,
+                                LOCAL_OUTER_6_NEW,
+                                REMOTE_OUTER_6_NEW,
+                                useEncapAfterMigrate,
+                                transportInTunnelMode,
+                                sTunWrapperNew.utils,
+                                tunnelIface,
+                                ipsecNetwork);
+                    }
 
                     return 0;
                 }
@@ -581,7 +656,8 @@
                             buildIpSecTransform(sContext, inTransportSpi, null, remoteInner);
                     IpSecTransform outTransportTransform =
                             buildIpSecTransform(sContext, outTransportSpi, null, localInner);
-                    UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
+                    UdpEncapsulationSocket encapSocket =
+                            useEncap ? openUdpEncapsulationSocket(outerFamily) : null) {
 
                 // Configure tunnel mode Transform parameters
                 IpSecTransform.Builder transformBuilder = new IpSecTransform.Builder(sContext);
@@ -591,7 +667,7 @@
                         new IpSecAlgorithm(
                                 IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4));
 
-                if (useEncap) {
+                if (encapSocket != null) {
                     transformBuilder.setIpv4Encapsulation(encapSocket, encapSocket.getPort());
                 }
 
@@ -615,19 +691,152 @@
                                     spi,
                                     localInner,
                                     remoteInner,
-                                    localOuter,
-                                    remoteOuter,
                                     inTransportTransform,
                                     outTransportTransform,
-                                    useEncap ? encapSocket.getPort() : 0,
-                                    0,
-                                    expectedPacketSize)
-                            .run(ipsecNetwork, tunnelIface, tunUtils);
+                                    encapSocket,
+                                    0)
+                            .run(
+                                    ipsecNetwork,
+                                    tunnelIface,
+                                    tunUtils,
+                                    inTransform,
+                                    outTransform,
+                                    localOuter,
+                                    remoteOuter,
+                                    1 /* seqNum */);
                 }
             }
         }
     }
 
+    private class MigrateTunnelModeIpSecTransformTestRunnableFactory
+            implements IpSecTunnelTestRunnableFactory {
+        private final IpSecTunnelTestRunnableFactory mTestRunnableFactory;
+
+        MigrateTunnelModeIpSecTransformTestRunnableFactory(boolean isOutputTest) {
+            if (isOutputTest) {
+                mTestRunnableFactory = new OutputIpSecTunnelTestRunnableFactory();
+            } else {
+                mTestRunnableFactory = new InputPacketGeneratorIpSecTunnelTestRunnableFactory();
+            }
+        }
+
+        @Override
+        public IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
+                boolean transportInTunnelMode,
+                int spi,
+                InetAddress localInner,
+                InetAddress remoteInner,
+                IpSecTransform inTransportTransform,
+                IpSecTransform outTransportTransform,
+                UdpEncapsulationSocket encapSocket,
+                int unusedInnerSocketPort) {
+            return new IpSecTunnelTestRunnable() {
+                @Override
+                public int run(
+                        Network ipsecNetwork,
+                        IpSecTunnelInterface tunnelIface,
+                        TunUtils tunUtils,
+                        IpSecTransform inTunnelTransform,
+                        IpSecTransform outTunnelTransform,
+                        InetAddress localOuter,
+                        InetAddress remoteOuter,
+                        int seqNum)
+                        throws Exception {
+                    final IpSecTunnelTestRunnable testRunnable =
+                            mTestRunnableFactory.getIpSecTunnelTestRunnable(
+                                    transportInTunnelMode,
+                                    spi,
+                                    localInner,
+                                    remoteInner,
+                                    inTransportTransform,
+                                    outTransportTransform,
+                                    encapSocket,
+                                    unusedInnerSocketPort);
+                    testRunnable.run(
+                            ipsecNetwork,
+                            tunnelIface,
+                            tunUtils,
+                            inTunnelTransform,
+                            outTunnelTransform,
+                            localOuter,
+                            remoteOuter,
+                            seqNum++);
+
+                    tunnelIface.setUnderlyingNetwork(sTunWrapperNew.network);
+
+                    final boolean useEncap = encapSocket != null;
+                    if (useEncap) {
+                        sTunWrapperNew.network.bindSocket(encapSocket.getFileDescriptor());
+                    }
+
+                    // Updating UDP encapsulation socket is not supported. Thus this runnable will
+                    // only cover 1) migration from non-encap to non-encap and 2) migration from
+                    // encap to encap with the same family
+                    if (!useEncap || localOuter instanceof Inet4Address) {
+                        checkMigrateTunnelModeTransform(
+                                testRunnable,
+                                inTunnelTransform,
+                                outTunnelTransform,
+                                tunnelIface,
+                                ipsecNetwork,
+                                sTunWrapperNew.utils,
+                                LOCAL_OUTER_4_NEW,
+                                REMOTE_OUTER_4_NEW,
+                                seqNum++);
+                    }
+                    if (!useEncap || localOuter instanceof Inet6Address) {
+                        checkMigrateTunnelModeTransform(
+                                testRunnable,
+                                inTunnelTransform,
+                                outTunnelTransform,
+                                tunnelIface,
+                                ipsecNetwork,
+                                sTunWrapperNew.utils,
+                                LOCAL_OUTER_6_NEW,
+                                REMOTE_OUTER_6_NEW,
+                                seqNum++);
+                    }
+
+                    // Unused return value for MigrateTunnelModeIpSecTransformTest
+                    return 0;
+                }
+            };
+        }
+
+        private void checkMigrateTunnelModeTransform(
+                IpSecTunnelTestRunnable testRunnable,
+                IpSecTransform inTunnelTransform,
+                IpSecTransform outTunnelTransform,
+                IpSecTunnelInterface tunnelIface,
+                Network ipsecNetwork,
+                TunUtils tunUtils,
+                InetAddress newLocalOuter,
+                InetAddress newRemoteOuter,
+                int seqNum)
+                throws Exception {
+            mISM.startTunnelModeTransformMigration(
+                    inTunnelTransform, newRemoteOuter, newLocalOuter);
+            mISM.startTunnelModeTransformMigration(
+                    outTunnelTransform, newLocalOuter, newRemoteOuter);
+
+            mISM.applyTunnelModeTransform(
+                    tunnelIface, IpSecManager.DIRECTION_IN, inTunnelTransform);
+            mISM.applyTunnelModeTransform(
+                    tunnelIface, IpSecManager.DIRECTION_OUT, outTunnelTransform);
+
+            testRunnable.run(
+                    ipsecNetwork,
+                    tunnelIface,
+                    tunUtils,
+                    inTunnelTransform,
+                    outTunnelTransform,
+                    newLocalOuter,
+                    newRemoteOuter,
+                    seqNum);
+        }
+    }
+
     private void checkTunnelOutput(
             int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
             throws Exception {
@@ -651,17 +860,36 @@
     }
 
     private void checkMigrateTunnelOutput(
-            int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
+            int innerFamily,
+            int outerFamily,
+            boolean useEncap,
+            boolean transportInTunnelMode,
+            boolean isEncapTypeChanged)
             throws Exception {
         checkTunnel(
                 innerFamily,
                 outerFamily,
                 useEncap,
                 transportInTunnelMode,
-                new MigrateIpSecTunnelTestRunnableFactory(true));
+                new MigrateIpSecTunnelTestRunnableFactory(true, isEncapTypeChanged));
     }
 
     private void checkMigrateTunnelInput(
+            int innerFamily,
+            int outerFamily,
+            boolean useEncap,
+            boolean transportInTunnelMode,
+            boolean isEncapTypeChanged)
+            throws Exception {
+        checkTunnel(
+                innerFamily,
+                outerFamily,
+                useEncap,
+                transportInTunnelMode,
+                new MigrateIpSecTunnelTestRunnableFactory(false, isEncapTypeChanged));
+    }
+
+    private void checkMigrateTunnelModeTransformOutput(
             int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
             throws Exception {
         checkTunnel(
@@ -669,7 +897,18 @@
                 outerFamily,
                 useEncap,
                 transportInTunnelMode,
-                new MigrateIpSecTunnelTestRunnableFactory(false));
+                new MigrateTunnelModeIpSecTransformTestRunnableFactory(true /* isOutputTest */));
+    }
+
+    private void checkMigrateTunnelModeTransformInput(
+            int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
+            throws Exception {
+        checkTunnel(
+                innerFamily,
+                outerFamily,
+                useEncap,
+                transportInTunnelMode,
+                new MigrateTunnelModeIpSecTransformTestRunnableFactory(false /* isOutputTest */));
     }
 
     /**
@@ -701,7 +940,8 @@
                         buildIpSecTransform(sContext, inTransportSpi, null, remoteInner);
                 IpSecTransform outTransportTransform =
                         buildIpSecTransform(sContext, outTransportSpi, null, localInner);
-                UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
+                UdpEncapsulationSocket encapSocket =
+                        useEncap ? openUdpEncapsulationSocket(outerFamily) : null) {
 
             // Run output direction tests
             IpSecTunnelTestRunnable outputIpSecTunnelTestRunnable =
@@ -711,22 +951,19 @@
                                     spi,
                                     localInner,
                                     remoteInner,
-                                    localOuter,
-                                    remoteOuter,
                                     inTransportTransform,
                                     outTransportTransform,
-                                    useEncap ? encapSocket.getPort() : 0,
-                                    0,
-                                    expectedPacketSize);
+                                    encapSocket,
+                                    0);
             int innerSocketPort =
                     buildTunnelNetworkAndRunTests(
-                    localInner,
-                    remoteInner,
-                    localOuter,
-                    remoteOuter,
-                    spi,
-                    useEncap ? encapSocket : null,
-                    outputIpSecTunnelTestRunnable);
+                            localInner,
+                            remoteInner,
+                            localOuter,
+                            remoteOuter,
+                            spi,
+                            encapSocket,
+                            outputIpSecTunnelTestRunnable);
 
             // Input direction tests, with matching inner socket ports.
             IpSecTunnelTestRunnable inputIpSecTunnelTestRunnable =
@@ -736,20 +973,17 @@
                                     spi,
                                     remoteInner,
                                     localInner,
-                                    localOuter,
-                                    remoteOuter,
                                     inTransportTransform,
                                     outTransportTransform,
-                                    useEncap ? encapSocket.getPort() : 0,
-                                    innerSocketPort,
-                                    expectedPacketSize);
+                                    encapSocket,
+                                    innerSocketPort);
             buildTunnelNetworkAndRunTests(
                     remoteInner,
                     localInner,
                     localOuter,
                     remoteOuter,
                     spi,
-                    useEncap ? encapSocket : null,
+                    encapSocket,
                     inputIpSecTunnelTestRunnable);
         }
     }
@@ -783,7 +1017,8 @@
                         buildIpSecTransform(sContext, inTransportSpi, null, remoteInner);
                 IpSecTransform outTransportTransform =
                         buildIpSecTransform(sContext, outTransportSpi, null, localInner);
-                UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
+                UdpEncapsulationSocket encapSocket =
+                        useEncap ? openUdpEncapsulationSocket(outerFamily) : null) {
 
             buildTunnelNetworkAndRunTests(
                     localInner,
@@ -791,19 +1026,16 @@
                     localOuter,
                     remoteOuter,
                     spi,
-                    useEncap ? encapSocket : null,
+                    encapSocket,
                     factory.getIpSecTunnelTestRunnable(
                             transportInTunnelMode,
                             spi,
                             localInner,
                             remoteInner,
-                            localOuter,
-                            remoteOuter,
                             inTransportTransform,
                             outTransportTransform,
-                            useEncap ? encapSocket.getPort() : 0,
-                            0,
-                            expectedPacketSize));
+                            encapSocket,
+                            0));
         }
     }
 
@@ -851,6 +1083,7 @@
 
             if (encapSocket != null) {
                 transformBuilder.setIpv4Encapsulation(encapSocket, encapSocket.getPort());
+                sTunWrapper.network.bindSocket(encapSocket.getFileDescriptor());
             }
 
             // Apply transform and check that traffic is properly encrypted
@@ -862,7 +1095,16 @@
                 mISM.applyTunnelModeTransform(
                         tunnelIface, IpSecManager.DIRECTION_OUT, outTransform);
 
-                innerSocketPort = test.run(testNetwork, tunnelIface, sTunWrapper.utils);
+                innerSocketPort =
+                        test.run(
+                                testNetwork,
+                                tunnelIface,
+                                sTunWrapper.utils,
+                                inTransform,
+                                outTransform,
+                                localOuter,
+                                remoteOuter,
+                                1 /* seqNum */);
             }
 
             // Teardown the test network
@@ -901,13 +1143,14 @@
     }
 
     private EspHeader buildTransportModeEspPacket(
-            int spi, InetAddress src, InetAddress dst, int port, Payload payload) throws Exception {
+            int spi, int seqNum, InetAddress src, InetAddress dst, Payload payload)
+            throws Exception {
         IpHeader preEspIpHeader = getIpHeader(payload.getProtocolId(), src, dst, payload);
 
         return new EspHeader(
                 payload.getProtocolId(),
                 spi,
-                1, // sequence number
+                seqNum,
                 CRYPT_KEY, // Same key for auth and crypt
                 payload.getPacketBytes(preEspIpHeader));
     }
@@ -920,13 +1163,14 @@
             InetAddress dstOuter,
             int port,
             int encapPort,
+            int seqNum,
             Payload payload)
             throws Exception {
         IpHeader innerIp = getIpHeader(payload.getProtocolId(), srcInner, dstInner, payload);
         return new EspHeader(
                 innerIp.getProtocolId(),
                 spi,
-                1, // sequence number
+                seqNum, // sequence number
                 CRYPT_KEY, // Same key for auth and crypt
                 innerIp.getPacketBytes());
     }
@@ -950,13 +1194,14 @@
             InetAddress srcOuter,
             InetAddress dstOuter,
             int port,
-            int encapPort)
+            int encapPort,
+            int seqNum)
             throws Exception {
         UdpHeader udp = new UdpHeader(port, port, new BytePayload(TEST_DATA));
 
         EspHeader espPayload =
                 buildTunnelModeEspPacket(
-                        spi, srcInner, dstInner, srcOuter, dstOuter, port, encapPort, udp);
+                        spi, srcInner, dstInner, srcOuter, dstOuter, port, encapPort, seqNum, udp);
         return maybeEncapPacket(srcOuter, dstOuter, encapPort, espPayload).getPacketBytes();
     }
 
@@ -968,11 +1213,13 @@
             InetAddress srcOuter,
             InetAddress dstOuter,
             int port,
-            int encapPort)
+            int encapPort,
+            int seqNum)
             throws Exception {
         UdpHeader udp = new UdpHeader(port, port, new BytePayload(TEST_DATA));
 
-        EspHeader espPayload = buildTransportModeEspPacket(spiInner, srcInner, dstInner, port, udp);
+        EspHeader espPayload =
+                buildTransportModeEspPacket(spiInner, seqNum, srcInner, dstInner, udp);
         espPayload =
                 buildTunnelModeEspPacket(
                         spiOuter,
@@ -982,16 +1229,65 @@
                         dstOuter,
                         port,
                         encapPort,
+                        seqNum,
                         espPayload);
         return maybeEncapPacket(srcOuter, dstOuter, encapPort, espPayload).getPacketBytes();
     }
 
     private void doTestMigrateTunnel(
+            int innerFamily,
+            int outerFamily,
+            boolean useEncap,
+            boolean transportInTunnelMode,
+            boolean testEncapTypeChange)
+            throws Exception {
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+        checkMigrateTunnelOutput(
+                innerFamily, outerFamily, useEncap, transportInTunnelMode, testEncapTypeChange);
+        checkMigrateTunnelInput(
+                innerFamily, outerFamily, useEncap, transportInTunnelMode, testEncapTypeChange);
+    }
+
+    private void doTestMigrateTunnel(
+            int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
+            throws Exception {
+        doTestMigrateTunnel(
+                innerFamily,
+                outerFamily,
+                useEncap,
+                transportInTunnelMode,
+                false /* testEncapTypeChange */);
+    }
+
+    private void doTestMigrateTunnelWithEncapTypeChange(
+            int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
+            throws Exception {
+        doTestMigrateTunnel(
+                innerFamily,
+                outerFamily,
+                useEncap,
+                transportInTunnelMode,
+                true /* testEncapTypeChange */);
+    }
+
+    private void doTestMigrateTunnelModeTransform(
             int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
             throws Exception {
         assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
-        checkTunnelOutput(innerFamily, outerFamily, useEncap, transportInTunnelMode);
-        checkTunnelInput(innerFamily, outerFamily, useEncap, transportInTunnelMode);
+        assumeTrue(mCtsNetUtils.hasIpsecTunnelMigrateFeature());
+        checkMigrateTunnelModeTransformOutput(
+                innerFamily, outerFamily, useEncap, transportInTunnelMode);
+        checkMigrateTunnelModeTransformInput(
+                innerFamily, outerFamily, useEncap, transportInTunnelMode);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testHasIpSecTunnelMigrateFeature() throws Exception {
+        // FEATURE_IPSEC_TUNNEL_MIGRATION is required when VSR API is U/U+
+        if (getVsrApiLevel() > Build.VERSION_CODES.TIRAMISU) {
+            assertTrue(mCtsNetUtils.hasIpsecTunnelMigrateFeature());
+        }
     }
 
     // Transport-in-Tunnel mode tests
@@ -1008,6 +1304,12 @@
         doTestMigrateTunnel(AF_INET, AF_INET, false, true);
     }
 
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test
+    public void testMigrateTransportInTunnelModeV4InV4_EncapTypeChange() throws Exception {
+        doTestMigrateTunnelWithEncapTypeChange(AF_INET, AF_INET, false, true);
+    }
+
     @Test
     public void testTransportInTunnelModeV4InV4Reflected() throws Exception {
         assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -1027,6 +1329,12 @@
         doTestMigrateTunnel(AF_INET, AF_INET, true, true);
     }
 
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test
+    public void testMigrateTransportInTunnelModeV4InV4UdpEncap_EncapTypeChange() throws Exception {
+        doTestMigrateTunnelWithEncapTypeChange(AF_INET, AF_INET, true, true);
+    }
+
     @Test
     public void testTransportInTunnelModeV4InV4UdpEncapReflected() throws Exception {
         assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -1046,6 +1354,12 @@
         doTestMigrateTunnel(AF_INET, AF_INET6, false, true);
     }
 
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test
+    public void testMigrateTransportInTunnelModeV4InV6_EncapTypeChange() throws Exception {
+        doTestMigrateTunnelWithEncapTypeChange(AF_INET, AF_INET6, false, true);
+    }
+
     @Test
     public void testTransportInTunnelModeV4InV6Reflected() throws Exception {
         assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -1065,6 +1379,12 @@
         doTestMigrateTunnel(AF_INET6, AF_INET, false, true);
     }
 
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test
+    public void testMigrateTransportInTunnelModeV6InV4_EncapTypeChange() throws Exception {
+        doTestMigrateTunnelWithEncapTypeChange(AF_INET6, AF_INET, false, true);
+    }
+
     @Test
     public void testTransportInTunnelModeV6InV4Reflected() throws Exception {
         assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -1084,6 +1404,12 @@
         doTestMigrateTunnel(AF_INET6, AF_INET, true, true);
     }
 
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test
+    public void testMigrateTransportInTunnelModeV6InV4UdpEncap_EncapTypeChange() throws Exception {
+        doTestMigrateTunnelWithEncapTypeChange(AF_INET6, AF_INET, true, true);
+    }
+
     @Test
     public void testTransportInTunnelModeV6InV4UdpEncapReflected() throws Exception {
         assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -1103,6 +1429,12 @@
         doTestMigrateTunnel(AF_INET, AF_INET6, false, true);
     }
 
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test
+    public void testMigrateTransportInTunnelModeV6InV6_EncapTypeChange() throws Exception {
+        doTestMigrateTunnelWithEncapTypeChange(AF_INET, AF_INET6, false, true);
+    }
+
     @Test
     public void testTransportInTunnelModeV6InV6Reflected() throws Exception {
         assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -1123,6 +1455,12 @@
         doTestMigrateTunnel(AF_INET, AF_INET, false, false);
     }
 
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test
+    public void testMigrateTunnelV4InV4_EncapTypeChange() throws Exception {
+        doTestMigrateTunnelWithEncapTypeChange(AF_INET, AF_INET, false, false);
+    }
+
     @Test
     public void testTunnelV4InV4Reflected() throws Exception {
         assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -1142,6 +1480,12 @@
         doTestMigrateTunnel(AF_INET, AF_INET, true, false);
     }
 
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test
+    public void testMigrateTunnelV4InV4UdpEncap_EncapTypeChange() throws Exception {
+        doTestMigrateTunnelWithEncapTypeChange(AF_INET, AF_INET, true, false);
+    }
+
     @Test
     public void testTunnelV4InV4UdpEncapReflected() throws Exception {
         assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -1161,6 +1505,12 @@
         doTestMigrateTunnel(AF_INET, AF_INET6, false, false);
     }
 
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test
+    public void testMigrateTunnelV4InV6_EncapTypeChange() throws Exception {
+        doTestMigrateTunnelWithEncapTypeChange(AF_INET, AF_INET6, false, false);
+    }
+
     @Test
     public void testTunnelV4InV6Reflected() throws Exception {
         assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -1180,6 +1530,12 @@
         doTestMigrateTunnel(AF_INET6, AF_INET, false, false);
     }
 
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test
+    public void testMigrateTunnelV6InV4_EncapTypeChange() throws Exception {
+        doTestMigrateTunnelWithEncapTypeChange(AF_INET6, AF_INET, false, false);
+    }
+
     @Test
     public void testTunnelV6InV4Reflected() throws Exception {
         assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -1199,6 +1555,12 @@
         doTestMigrateTunnel(AF_INET6, AF_INET, true, false);
     }
 
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test
+    public void testMigrateTunnelV6InV4UdpEncap_EncapTypeChange() throws Exception {
+        doTestMigrateTunnelWithEncapTypeChange(AF_INET6, AF_INET, true, false);
+    }
+
     @Test
     public void testTunnelV6InV4UdpEncapReflected() throws Exception {
         assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
@@ -1218,9 +1580,115 @@
         doTestMigrateTunnel(AF_INET6, AF_INET6, false, false);
     }
 
+    @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test
+    public void testMigrateTunnelV6InV6_EncapTypeChange() throws Exception {
+        doTestMigrateTunnelWithEncapTypeChange(AF_INET6, AF_INET6, false, false);
+    }
+
     @Test
     public void testTunnelV6InV6Reflected() throws Exception {
         assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
         checkTunnelReflected(AF_INET6, AF_INET6, false, false);
     }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTransportInTunnelModeV4InV4() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET, AF_INET, false, true);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTransportInTunnelModeV6InV4() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET6, AF_INET, false, true);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTransportInTunnelModeV4InV6() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET, AF_INET6, false, true);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTransportInTunnelModeV6InV6() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET, AF_INET6, false, true);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTransportInTunnelModeV4InV4UdpEncap() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET, AF_INET, true, true);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTransportInTunnelModeV6InV4UdpEncap() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET6, AF_INET, true, true);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTransportInTunnelModeV4InV6UdpEncap() throws Exception {
+        assumeExperimentalIpv6UdpEncapSupported();
+        doTestMigrateTunnelModeTransform(AF_INET, AF_INET6, true, true);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTransportInTunnelModeV6InV6UdpEncap() throws Exception {
+        assumeExperimentalIpv6UdpEncapSupported();
+        doTestMigrateTunnelModeTransform(AF_INET6, AF_INET6, true, true);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTunnelV4InV4() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET, AF_INET, false, false);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTunnelV6InV4() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET6, AF_INET, false, false);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTunnelV4InV6() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET, AF_INET6, false, false);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTunnelV6InV6() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET6, AF_INET6, false, false);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTunnelV4InV4UdpEncap() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET, AF_INET, true, false);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTunnelV6InV4UdpEncap() throws Exception {
+        doTestMigrateTunnelModeTransform(AF_INET6, AF_INET, true, false);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTunnelV4InV6UdpEncap() throws Exception {
+        assumeExperimentalIpv6UdpEncapSupported();
+        doTestMigrateTunnelModeTransform(AF_INET, AF_INET6, true, false);
+    }
+
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    public void testMigrateTransformTunnelV6InV6UdpEncap() throws Exception {
+        assumeExperimentalIpv6UdpEncapSupported();
+        doTestMigrateTunnelModeTransform(AF_INET6, AF_INET6, true, false);
+    }
 }
diff --git a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
index 691ab99..17a9ca2 100644
--- a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
+++ b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
@@ -18,21 +18,18 @@
 
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 
-import android.content.Context;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkUtils;
 import android.net.cts.util.CtsNetUtils;
 import android.platform.test.annotations.AppModeFull;
-import android.provider.Settings;
 import android.system.ErrnoException;
 import android.system.OsConstants;
 import android.test.AndroidTestCase;
 
-import java.util.ArrayList;
-
 public class MultinetworkApiTest extends AndroidTestCase {
 
     static {
@@ -75,26 +72,8 @@
         super.tearDown();
     }
 
-    private Network[] getTestableNetworks() {
-        final ArrayList<Network> testableNetworks = new ArrayList<Network>();
-        for (Network network : mCM.getAllNetworks()) {
-            final NetworkCapabilities nc = mCM.getNetworkCapabilities(network);
-            if (nc != null
-                    && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                    && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
-                testableNetworks.add(network);
-            }
-        }
-
-        assertTrue(
-                "This test requires that at least one network be connected. " +
-                "Please ensure that the device is connected to a network.",
-                testableNetworks.size() >= 1);
-        return testableNetworks.toArray(new Network[0]);
-    }
-
     public void testGetaddrinfo() throws ErrnoException {
-        for (Network network : getTestableNetworks()) {
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
             int errno = runGetaddrinfoCheck(network.getNetworkHandle());
             if (errno != 0) {
                 throw new ErrnoException(
@@ -109,7 +88,7 @@
         assertNull(mCM.getProcessDefaultNetwork());
         assertEquals(0, NetworkUtils.getBoundNetworkForProcess());
 
-        for (Network network : getTestableNetworks()) {
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
             mCM.setProcessDefaultNetwork(null);
             assertNull(mCM.getProcessDefaultNetwork());
 
@@ -128,7 +107,7 @@
             mCM.setProcessDefaultNetwork(null);
         }
 
-        for (Network network : getTestableNetworks()) {
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
             NetworkUtils.bindProcessToNetwork(0);
             assertNull(mCM.getBoundNetworkForProcess());
 
@@ -148,7 +127,7 @@
 
     @AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
     public void testSetsocknetwork() throws ErrnoException {
-        for (Network network : getTestableNetworks()) {
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
             int errno = runSetsocknetwork(network.getNetworkHandle());
             if (errno != 0) {
                 throw new ErrnoException(
@@ -158,7 +137,7 @@
     }
 
     public void testNativeDatagramTransmission() throws ErrnoException {
-        for (Network network : getTestableNetworks()) {
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
             int errno = runDatagramCheck(network.getNetworkHandle());
             if (errno != 0) {
                 throw new ErrnoException(
@@ -181,7 +160,7 @@
 
     public void testNetworkHandle() {
         // Test Network -> NetworkHandle -> Network results in the same Network.
-        for (Network network : getTestableNetworks()) {
+        for (Network network : mCtsNetUtils.getTestableNetworks()) {
             long networkHandle = network.getNetworkHandle();
             Network newNetwork = Network.fromNetworkHandle(networkHandle);
             assertEquals(newNetwork, network);
@@ -203,7 +182,7 @@
     }
 
     public void testResNApi() throws Exception {
-        final Network[] testNetworks = getTestableNetworks();
+        final Network[] testNetworks = mCtsNetUtils.getTestableNetworks();
 
         for (Network network : testNetworks) {
             // Throws AssertionError directly in jni function if test fail.
@@ -229,7 +208,7 @@
         // b/144521720
         try {
             mCtsNetUtils.setPrivateDnsStrictMode(GOOGLE_PRIVATE_DNS_SERVER);
-            for (Network network : getTestableNetworks()) {
+            for (Network network : mCtsNetUtils.getTestableNetworks()) {
               // Wait for private DNS setting to propagate.
               mCtsNetUtils.awaitPrivateDnsSetting("NxDomain test wait private DNS setting timeout",
                         network, GOOGLE_PRIVATE_DNS_SERVER, true);
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 6df71c8..cf5fc50 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -29,9 +29,9 @@
 import android.net.NattKeepalivePacketData
 import android.net.Network
 import android.net.NetworkAgent
-import android.net.NetworkAgentConfig
 import android.net.NetworkAgent.INVALID_NETWORK
 import android.net.NetworkAgent.VALID_NETWORK
+import android.net.NetworkAgentConfig
 import android.net.NetworkCapabilities
 import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED
@@ -46,21 +46,23 @@
 import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
 import android.net.NetworkCapabilities.TRANSPORT_TEST
-import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.NetworkCapabilities.TRANSPORT_VPN
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.NetworkInfo
 import android.net.NetworkProvider
 import android.net.NetworkReleasedException
 import android.net.NetworkRequest
 import android.net.NetworkScore
-import android.net.RouteInfo
 import android.net.QosCallback
-import android.net.QosCallbackException
 import android.net.QosCallback.QosCallbackRegistrationException
+import android.net.QosCallbackException
 import android.net.QosSession
 import android.net.QosSessionAttributes
 import android.net.QosSocketInfo
+import android.net.RouteInfo
 import android.net.SocketKeepalive
+import android.net.TestNetworkInterface
+import android.net.TestNetworkManager
 import android.net.Uri
 import android.net.VpnManager
 import android.net.VpnTransportInfo
@@ -71,6 +73,7 @@
 import android.os.Handler
 import android.os.HandlerThread
 import android.os.Message
+import android.os.Process
 import android.os.SystemClock
 import android.platform.test.annotations.AppModeFull
 import android.system.OsConstants.IPPROTO_TCP
@@ -89,6 +92,7 @@
 import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.RecorderCallback.CallbackEntry.Available
 import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
 import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
 import com.android.testutils.RecorderCallback.CallbackEntry.Losing
 import com.android.testutils.RecorderCallback.CallbackEntry.Lost
@@ -108,17 +112,6 @@
 import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStatus
 import com.android.testutils.TestableNetworkCallback
 import com.android.testutils.assertThrows
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.argThat
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.timeout
-import org.mockito.Mockito.verify
 import java.io.Closeable
 import java.io.IOException
 import java.net.DatagramSocket
@@ -136,6 +129,17 @@
 import kotlin.test.assertNull
 import kotlin.test.assertTrue
 import kotlin.test.fail
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.argThat
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
 
 // This test doesn't really have a constraint on how fast the methods should return. If it's
 // going to fail, it will simply wait forever, so setting a high timeout lowers the flake ratio
@@ -168,6 +172,7 @@
 // for modules other than Connectivity does not provide much value. Only run them in connectivity
 // module MTS, so the tests only need to cover the case of an updated NetworkAgent.
 @ConnectivityModuleTest
+@AppModeFull(reason = "Instant apps can't use NetworkAgent because it needs NETWORK_FACTORY'.")
 class NetworkAgentTest {
     private val LOCAL_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1")
     private val REMOTE_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.2")
@@ -178,6 +183,7 @@
     private val agentsToCleanUp = mutableListOf<NetworkAgent>()
     private val callbacksToCleanUp = mutableListOf<TestableNetworkCallback>()
     private var qosTestSocket: Closeable? = null // either Socket or DatagramSocket
+    private val ifacesToCleanUp = mutableListOf<TestNetworkInterface>()
 
     @Before
     fun setUp() {
@@ -189,8 +195,10 @@
     fun tearDown() {
         agentsToCleanUp.forEach { it.unregister() }
         callbacksToCleanUp.forEach { mCM.unregisterNetworkCallback(it) }
+        ifacesToCleanUp.forEach { it.fileDescriptor.close() }
         qosTestSocket?.close()
         mHandlerThread.quitSafely()
+        mHandlerThread.join()
         instrumentation.getUiAutomation().dropShellPermissionIdentity()
     }
 
@@ -268,7 +276,7 @@
         removeCapability(NET_CAPABILITY_INTERNET)
         addCapability(NET_CAPABILITY_NOT_SUSPENDED)
         addCapability(NET_CAPABILITY_NOT_ROAMING)
-        addCapability(NET_CAPABILITY_NOT_VPN)
+        if (!transports.contains(TRANSPORT_VPN)) addCapability(NET_CAPABILITY_NOT_VPN)
         if (SdkLevel.isAtLeastS()) {
             addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
         }
@@ -303,7 +311,7 @@
         context: Context = realContext,
         specifier: String? = UUID.randomUUID().toString(),
         initialConfig: NetworkAgentConfig? = null,
-        expectedInitSignalStrengthThresholds: IntArray? = intArrayOf(),
+        expectedInitSignalStrengthThresholds: IntArray = intArrayOf(),
         transports: IntArray = intArrayOf()
     ): Pair<TestableNetworkAgent, TestableNetworkCallback> {
         val callback = TestableNetworkCallback()
@@ -316,8 +324,7 @@
         agent.register()
         agent.markConnected()
         agent.expectCallback<OnNetworkCreated>()
-        agent.expectSignalStrengths(expectedInitSignalStrengthThresholds)
-        agent.expectValidationBypassedStatus()
+        agent.expectPostConnectionCallbacks(expectedInitSignalStrengthThresholds)
         callback.expectAvailableThenValidatedCallbacks(agent.network!!)
         return agent to callback
     }
@@ -335,6 +342,19 @@
         mFakeConnectivityService.connect(it.registerForTest(Network(FAKE_NET_ID)))
     }
 
+    private fun TestableNetworkAgent.expectPostConnectionCallbacks(
+        thresholds: IntArray = intArrayOf()
+    ) {
+        expectSignalStrengths(thresholds)
+        expectValidationBypassedStatus()
+        assertNoCallback()
+    }
+
+    private fun createTunInterface(): TestNetworkInterface = realContext.getSystemService(
+                TestNetworkManager::class.java)!!.createTunInterface(emptyList()).also {
+            ifacesToCleanUp.add(it)
+    }
+
     fun assertLinkPropertiesEventually(
         n: Network,
         description: String,
@@ -391,9 +411,7 @@
             val nc = NetworkCapabilities(agent.nc)
             nc.addCapability(NET_CAPABILITY_NOT_METERED)
             agent.sendNetworkCapabilities(nc)
-            callback.expectCapabilitiesThat(agent.network) {
-                it.hasCapability(NET_CAPABILITY_NOT_METERED)
-            }
+            callback.expectCaps(agent.network) { it.hasCapability(NET_CAPABILITY_NOT_METERED) }
             val networkInfo = mCM.getNetworkInfo(agent.network)
             assertEquals(subtypeUMTS, networkInfo.getSubtype())
             assertEquals(subtypeNameUMTS, networkInfo.getSubtypeName())
@@ -434,27 +452,28 @@
             (agent, callback) ->
             // Send signal strength and check that the callbacks are called appropriately.
             val nc = NetworkCapabilities(agent.nc)
+            val net = agent.network!!
             nc.setSignalStrength(20)
             agent.sendNetworkCapabilities(nc)
             callbacks.forEach { it.assertNoCallback(NO_CALLBACK_TIMEOUT) }
 
             nc.setSignalStrength(40)
             agent.sendNetworkCapabilities(nc)
-            callbacks[0].expectAvailableCallbacks(agent.network!!)
+            callbacks[0].expectAvailableCallbacks(net)
             callbacks[1].assertNoCallback(NO_CALLBACK_TIMEOUT)
             callbacks[2].assertNoCallback(NO_CALLBACK_TIMEOUT)
 
             nc.setSignalStrength(80)
             agent.sendNetworkCapabilities(nc)
-            callbacks[0].expectCapabilitiesThat(agent.network!!) { it.signalStrength == 80 }
-            callbacks[1].expectAvailableCallbacks(agent.network!!)
-            callbacks[2].expectAvailableCallbacks(agent.network!!)
+            callbacks[0].expectCaps(net) { it.signalStrength == 80 }
+            callbacks[1].expectAvailableCallbacks(net)
+            callbacks[2].expectAvailableCallbacks(net)
 
             nc.setSignalStrength(55)
             agent.sendNetworkCapabilities(nc)
-            callbacks[0].expectCapabilitiesThat(agent.network!!) { it.signalStrength == 55 }
-            callbacks[1].expectCapabilitiesThat(agent.network!!) { it.signalStrength == 55 }
-            callbacks[2].expect<Lost>(agent.network!!)
+            callbacks[0].expectCaps(net) { it.signalStrength == 55 }
+            callbacks[1].expectCaps(net) { it.signalStrength == 55 }
+            callbacks[2].expect<Lost>(net)
         }
         callbacks.forEach {
             mCM.unregisterNetworkCallback(it)
@@ -507,15 +526,11 @@
         val lp = LinkProperties(agent.lp)
         lp.setInterfaceName(ifaceName)
         agent.sendLinkProperties(lp)
-        callback.expectLinkPropertiesThat(agent.network!!) {
-            it.getInterfaceName() == ifaceName
-        }
+        callback.expect<LinkPropertiesChanged>(agent.network!!) { it.lp.interfaceName == ifaceName }
         val nc = NetworkCapabilities(agent.nc)
         nc.addCapability(NET_CAPABILITY_NOT_METERED)
         agent.sendNetworkCapabilities(nc)
-        callback.expectCapabilitiesThat(agent.network!!) {
-            it.hasCapability(NET_CAPABILITY_NOT_METERED)
-        }
+        callback.expectCaps(agent.network!!) { it.hasCapability(NET_CAPABILITY_NOT_METERED) }
     }
 
     private fun ncWithAllowedUids(vararg uids: Int) = NetworkCapabilities.Builder()
@@ -533,12 +548,12 @@
 
         // Make sure the UIDs have been ignored.
         callback.expect<Available>(agent.network!!)
-        callback.expectCapabilitiesThat(agent.network!!) {
+        callback.expectCaps(agent.network!!) {
             it.allowedUids.isEmpty() && !it.hasCapability(NET_CAPABILITY_VALIDATED)
         }
         callback.expect<LinkPropertiesChanged>(agent.network!!)
         callback.expect<BlockedStatus>(agent.network!!)
-        callback.expectCapabilitiesThat(agent.network!!) {
+        callback.expectCaps(agent.network!!) {
             it.allowedUids.isEmpty() && it.hasCapability(NET_CAPABILITY_VALIDATED)
         }
         callback.assertNoCallback(NO_CALLBACK_TIMEOUT)
@@ -582,8 +597,8 @@
         // tearDown() will unregister the requests and agents
     }
 
-    private fun hasAllTransports(nc: NetworkCapabilities?, transports: IntArray) =
-            nc != null && transports.all { nc.hasTransport(it) }
+    private fun NetworkCapabilities?.hasAllTransports(transports: IntArray) =
+            this != null && transports.all { hasTransport(it) }
 
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.R)
@@ -625,25 +640,25 @@
         assertEquals(mySessionId, (vpnNc.transportInfo as VpnTransportInfo).sessionId)
 
         val testAndVpn = intArrayOf(TRANSPORT_TEST, TRANSPORT_VPN)
-        assertTrue(hasAllTransports(vpnNc, testAndVpn))
+        assertTrue(vpnNc.hasAllTransports(testAndVpn))
         assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_VPN))
-        assertTrue(hasAllTransports(vpnNc, defaultNetworkTransports),
+        assertTrue(vpnNc.hasAllTransports(defaultNetworkTransports),
                 "VPN transports ${Arrays.toString(vpnNc.transportTypes)}" +
                         " lacking transports from ${Arrays.toString(defaultNetworkTransports)}")
 
         // Check that when no underlying networks are announced the underlying transport disappears.
         agent.setUnderlyingNetworks(listOf<Network>())
-        callback.expectCapabilitiesThat(agent.network!!) {
-            it.transportTypes.size == 2 && hasAllTransports(it, testAndVpn)
+        callback.expectCaps(agent.network!!) {
+            it.transportTypes.size == 2 && it.hasAllTransports(testAndVpn)
         }
 
         // Put the underlying network back and check that the underlying transport reappears.
         val expectedTransports = (defaultNetworkTransports.toSet() + TRANSPORT_TEST + TRANSPORT_VPN)
                 .toIntArray()
         agent.setUnderlyingNetworks(null)
-        callback.expectCapabilitiesThat(agent.network!!) {
+        callback.expectCaps(agent.network!!) {
             it.transportTypes.size == expectedTransports.size &&
-                    hasAllTransports(it, expectedTransports)
+                    it.hasAllTransports(expectedTransports)
         }
 
         // Check that some underlying capabilities are propagated.
@@ -757,7 +772,7 @@
         val nc1 = NetworkCapabilities(agent.nc)
                 .addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
         agent.sendNetworkCapabilities(nc1)
-        callback.expectCapabilitiesThat(agent.network!!) {
+        callback.expectCaps(agent.network!!) {
             it.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
         }
 
@@ -765,7 +780,7 @@
         val nc2 = NetworkCapabilities(agent.nc)
                 .removeCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
         agent.sendNetworkCapabilities(nc2)
-        callback.expectCapabilitiesThat(agent.network!!) {
+        callback.expectCaps(agent.network!!) {
             !it.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
         }
 
@@ -917,12 +932,10 @@
         val history = ArrayTrackRecord<CallbackEntry>().newReadHead()
 
         sealed class CallbackEntry {
-            data class OnQosSessionAvailable(val sess: QosSession, val attr: QosSessionAttributes)
-                : CallbackEntry()
-            data class OnQosSessionLost(val sess: QosSession)
-                : CallbackEntry()
-            data class OnError(val ex: QosCallbackException)
-                : CallbackEntry()
+            data class OnQosSessionAvailable(val sess: QosSession, val attr: QosSessionAttributes) :
+                CallbackEntry()
+            data class OnQosSessionLost(val sess: QosSession) : CallbackEntry()
+            data class OnError(val ex: QosCallbackException) : CallbackEntry()
         }
 
         override fun onQosSessionAvailable(sess: QosSession, attr: QosSessionAttributes) {
@@ -970,13 +983,11 @@
             .also { assertNotNull(agent.network?.bindSocket(it)) }
     }
 
-    @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
     @Test
     fun testQosCallbackRegisterAndUnregister() {
         validateQosCallbackRegisterAndUnregister(IPPROTO_TCP)
     }
 
-    @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
     @Test
     fun testQosCallbackRegisterAndUnregisterWithDatagramSocket() {
         validateQosCallbackRegisterAndUnregister(IPPROTO_UDP)
@@ -1013,13 +1024,11 @@
         }
     }
 
-    @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
     @Test
     fun testQosCallbackOnQosSession() {
         validateQosCallbackOnQosSession(IPPROTO_TCP)
     }
 
-    @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
     @Test
     fun testQosCallbackOnQosSessionWithDatagramSocket() {
         validateQosCallbackOnQosSession(IPPROTO_UDP)
@@ -1078,7 +1087,6 @@
         }
     }
 
-    @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
     @Test
     fun testQosCallbackOnError() {
         val (agent, qosTestSocket) = setupForQosSocket()
@@ -1117,7 +1125,6 @@
         }
     }
 
-    @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
     @Test
     fun testQosCallbackIdsAreMappedCorrectly() {
         val (agent, qosTestSocket) = setupForQosSocket()
@@ -1158,7 +1165,6 @@
         }
     }
 
-    @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
     @Test
     fun testQosCallbackWhenNetworkReleased() {
         val (agent, qosTestSocket) = setupForQosSocket()
@@ -1200,7 +1206,6 @@
         )
     }
 
-    @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
     @Test
     fun testUnregisterAfterReplacement() {
         // Keeps an eye on all test networks.
@@ -1297,8 +1302,12 @@
         requestNetwork(makeTestNetworkRequest(specifier = specifier6), callback)
         val agent6 = createNetworkAgent(specifier = specifier6)
         val network6 = agent6.register()
-        // No callbacks are sent, so check the LinkProperties to see if the network has connected.
-        assertLinkPropertiesEventuallyNotNull(agent6.network!!)
+        if (SdkLevel.isAtLeastU()) {
+            agent6.expectCallback<OnNetworkCreated>()
+        } else {
+            // No callbacks are sent, so check LinkProperties to wait for the network to be created.
+            assertLinkPropertiesEventuallyNotNull(agent6.network!!)
+        }
 
         // unregisterAfterReplacement tears down the network immediately.
         // Approximately check that this is the case by picking an unregister timeout that's longer
@@ -1307,8 +1316,9 @@
         val timeoutMs = agent6.DEFAULT_TIMEOUT_MS.toInt() + 1_000
         agent6.unregisterAfterReplacement(timeoutMs)
         agent6.expectCallback<OnNetworkUnwanted>()
-        if (!SdkLevel.isAtLeastT()) {
+        if (!SdkLevel.isAtLeastT() || SdkLevel.isAtLeastU()) {
             // Before T, onNetworkDestroyed is called even if the network was never created.
+            // On U+, the network was created by register(). Destroying it sends onNetworkDestroyed.
             agent6.expectCallback<OnNetworkDestroyed>()
         }
         // Poll for LinkProperties becoming null, because when onNetworkUnwanted is called, the
@@ -1330,14 +1340,10 @@
 
         val (wifiAgent, wifiNetwork) = connectNetwork(TRANSPORT_WIFI)
         testCallback.expectAvailableCallbacks(wifiNetwork, validated = true)
-        testCallback.expectCapabilitiesThat(wifiNetwork) {
-            it.hasCapability(NET_CAPABILITY_VALIDATED)
-        }
+        testCallback.expectCaps(wifiNetwork) { it.hasCapability(NET_CAPABILITY_VALIDATED) }
         matchAllCallback.expectAvailableCallbacks(wifiNetwork, validated = false)
         matchAllCallback.expect<Losing>(cellNetwork)
-        matchAllCallback.expectCapabilitiesThat(wifiNetwork) {
-            it.hasCapability(NET_CAPABILITY_VALIDATED)
-        }
+        matchAllCallback.expectCaps(wifiNetwork) { it.hasCapability(NET_CAPABILITY_VALIDATED) }
 
         wifiAgent.unregisterAfterReplacement(5_000 /* timeoutMillis */)
         wifiAgent.expectCallback<OnNetworkDestroyed>()
@@ -1385,4 +1391,101 @@
         callback.expect<Available>(agent.network!!)
         callback.eventuallyExpect<Lost> { it.network == agent.network }
     }
+
+    fun doTestNativeNetworkCreation(expectCreatedImmediately: Boolean, transports: IntArray) {
+        val iface = createTunInterface()
+        val ifName = iface.interfaceName
+        val nc = makeTestNetworkCapabilities(ifName, transports).also {
+            if (transports.contains(TRANSPORT_VPN)) {
+                val sessionId = "NetworkAgentTest-${Process.myPid()}"
+                it.transportInfo = VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, sessionId,
+                    /*bypassable=*/ false, /*longLivedTcpConnectionsExpensive=*/ false)
+                it.underlyingNetworks = listOf()
+            }
+        }
+        val lp = LinkProperties().apply {
+            interfaceName = ifName
+            addLinkAddress(LinkAddress("2001:db8::1/64"))
+            addRoute(RouteInfo(IpPrefix("2001:db8::/64"), null /* nextHop */, ifName))
+            addRoute(RouteInfo(IpPrefix("::/0"),
+                    InetAddresses.parseNumericAddress("fe80::abcd"),
+                    ifName))
+        }
+
+        // File a request containing the agent's specifier to receive callbacks and to ensure that
+        // the agent is not torn down due to being unneeded.
+        val request = makeTestNetworkRequest(specifier = ifName)
+        val requestCallback = TestableNetworkCallback()
+        requestNetwork(request, requestCallback)
+
+        val listenCallback = TestableNetworkCallback()
+        registerNetworkCallback(request, listenCallback)
+
+        // Register the NetworkAgent...
+        val agent = createNetworkAgent(realContext, initialNc = nc, initialLp = lp)
+        val network = agent.register()
+
+        // ... and then change the NetworkCapabilities and LinkProperties.
+        nc.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+        agent.sendNetworkCapabilities(nc)
+        lp.addLinkAddress(LinkAddress("192.0.2.2/25"))
+        lp.addRoute(RouteInfo(IpPrefix("192.0.2.0/25"), null /* nextHop */, ifName))
+        agent.sendLinkProperties(lp)
+
+        requestCallback.assertNoCallback()
+        listenCallback.assertNoCallback()
+        if (!expectCreatedImmediately) {
+            agent.assertNoCallback()
+            agent.markConnected()
+            agent.expectCallback<OnNetworkCreated>()
+        } else {
+            agent.expectCallback<OnNetworkCreated>()
+            agent.markConnected()
+        }
+        agent.expectPostConnectionCallbacks()
+
+        // onAvailable must be called only when the network connects, and no other callbacks may be
+        // called before that happens. The callbacks report the state of the network as it was when
+        // it connected, so they reflect the NC and LP changes made after registration.
+        requestCallback.expect<Available>(network)
+        listenCallback.expect<Available>(network)
+
+        requestCallback.expect<CapabilitiesChanged>(network) { it.caps.hasCapability(
+            NET_CAPABILITY_TEMPORARILY_NOT_METERED) }
+        listenCallback.expect<CapabilitiesChanged>(network) { it.caps.hasCapability(
+            NET_CAPABILITY_TEMPORARILY_NOT_METERED) }
+
+        requestCallback.expect<LinkPropertiesChanged>(network) { it.lp.equals(lp) }
+        listenCallback.expect<LinkPropertiesChanged>(network) { it.lp.equals(lp) }
+
+        requestCallback.expect<BlockedStatus>()
+        listenCallback.expect<BlockedStatus>()
+
+        // Except for network validation, ensure no more callbacks are sent.
+        requestCallback.expectCaps(network) {
+            it.hasCapability(NET_CAPABILITY_VALIDATED)
+        }
+        listenCallback.expectCaps(network) {
+            it.hasCapability(NET_CAPABILITY_VALIDATED)
+        }
+        unregister(agent)
+        // Lost implicitly checks that no further callbacks happened after connect.
+        requestCallback.expect<Lost>(network)
+        listenCallback.expect<Lost>(network)
+        assertNull(mCM.getLinkProperties(network))
+    }
+
+    @Test
+    fun testNativeNetworkCreation_PhysicalNetwork() {
+        // On T and below, the native network is only created when the agent connects.
+        // Starting in U, the native network is created as soon as the agent is registered.
+        doTestNativeNetworkCreation(expectCreatedImmediately = SdkLevel.isAtLeastU(),
+            intArrayOf(TRANSPORT_CELLULAR))
+    }
+
+    @Test
+    fun testNativeNetworkCreation_Vpn() {
+        // VPN networks are always created as soon as the agent is registered.
+        doTestNativeNetworkCreation(expectCreatedImmediately = true, intArrayOf(TRANSPORT_VPN))
+    }
 }
diff --git a/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
index eb41d71..2704dd3 100644
--- a/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
@@ -30,6 +30,7 @@
 import android.os.Build
 import android.os.Handler
 import android.os.HandlerThread
+import android.util.Log
 import androidx.test.InstrumentationRegistry
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
@@ -41,6 +42,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import java.util.Collections
 
 // This test doesn't really have a constraint on how fast the methods should return. If it's
 // going to fail, it will simply wait forever, so setting a high timeout lowers the flake ratio
@@ -64,10 +66,11 @@
 @IgnoreUpTo(Build.VERSION_CODES.R)
 @RunWith(DevSdkIgnoreRunner::class)
 class NetworkScoreTest {
+    private val TAG = javaClass.simpleName
     private val mCm = testContext.getSystemService(ConnectivityManager::class.java)
-    private val mHandlerThread = HandlerThread("${javaClass.simpleName} handler thread")
+    private val mHandlerThread = HandlerThread("$TAG handler thread")
     private val mHandler by lazy { Handler(mHandlerThread.looper) }
-    private val agentsToCleanUp = mutableListOf<NetworkAgent>()
+    private val agentsToCleanUp = Collections.synchronizedList(mutableListOf<NetworkAgent>())
     private val callbacksToCleanUp = mutableListOf<TestableNetworkCallback>()
 
     @Before
@@ -83,14 +86,18 @@
                     .addTransportType(NetworkCapabilities.TRANSPORT_TEST).build(), cb, mHandler
             )
         }
+        Log.i(TAG, "Teardown on thread ${System.identityHashCode(Thread.currentThread())} " +
+                "cleaning up ${agentsToCleanUp.size} agents")
         agentsToCleanUp.forEach {
+            Log.i(TAG, "Unregister agent for net ${it.network}")
             it.unregister()
             agentCleanUpCb.eventuallyExpect<CallbackEntry.Lost> { cb -> cb.network == it.network }
         }
         mCm.unregisterNetworkCallback(agentCleanUpCb)
 
-        mHandlerThread.quitSafely()
         callbacksToCleanUp.forEach { mCm.unregisterNetworkCallback(it) }
+        mHandlerThread.quitSafely()
+        mHandlerThread.join()
     }
 
     // Returns a networkCallback that sends onAvailable on the best network with TRANSPORT_TEST.
@@ -144,6 +151,8 @@
         val agent = object : NetworkAgent(context, looper, "NetworkScore test agent", nc,
                 LinkProperties(), score, config, NetworkProvider(context, looper,
                 "NetworkScore test provider")) {}.also {
+            Log.i(TAG, "Add on thread ${System.identityHashCode(Thread.currentThread())} " +
+                    "agent to clean up $it")
             agentsToCleanUp.add(it)
         }
         runWithShellPermissionIdentity({ agent.register() }, MANAGE_TEST_NETWORKS)
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
index f86c5cd..83b9b81 100644
--- a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -210,7 +210,6 @@
     private long mStartTime;
     private long mEndTime;
 
-    private long mBytesRead;
     private String mWriteSettingsMode;
     private String mUsageStatsMode;
 
@@ -229,6 +228,7 @@
             TrafficStats.setThreadStatsTag(NETWORK_TAG);
             urlc = (HttpURLConnection) network.openConnection(url);
             urlc.setConnectTimeout(TIMEOUT_MILLIS);
+            urlc.setReadTimeout(TIMEOUT_MILLIS);
             urlc.setUseCaches(false);
             // Disable compression so we generate enough traffic that assertWithinPercentage will
             // not be affected by the small amount of traffic (5-10kB) sent by the test harness.
@@ -236,11 +236,10 @@
             urlc.connect();
             boolean ping = urlc.getResponseCode() == 200;
             if (ping) {
-                in = new InputStreamReader(
-                        (InputStream) urlc.getContent());
-
-                mBytesRead = 0;
-                while (in.read() != -1) ++mBytesRead;
+                in = new InputStreamReader((InputStream) urlc.getContent());
+                // Since the test doesn't really care about the precise amount of data, instead
+                // of reading all contents, just read few bytes at the beginning.
+                in.read();
             }
         } catch (Exception e) {
             Log.i(LOG_TAG, "Badness during exercising remote server: " + e);
@@ -379,7 +378,7 @@
                 .build(), callback);
         synchronized (this) {
             try {
-                wait((int) (TIMEOUT_MILLIS * 1.2));
+                wait((int) (TIMEOUT_MILLIS * 2.4));
             } catch (InterruptedException e) {
             }
         }
@@ -394,7 +393,7 @@
         assertFalse(mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature()
                 + " is a reported system feature, "
                 + "however no corresponding connected network interface was found or the attempt "
-                + "to connect has timed out (timeout = " + TIMEOUT_MILLIS + "ms)."
+                + "to connect and read has timed out (timeout = " + (TIMEOUT_MILLIS * 2) + "ms)."
                 + mNetworkInterfacesToTest[networkTypeIndex].getErrorMessage(), hasFeature);
         return false;
     }
@@ -800,7 +799,7 @@
                 // harness, which is untagged, won't cause a failure.
                 long firstTotal = resultsWithTraffic.get(0).total;
                 for (QueryResult queryResult : resultsWithTraffic) {
-                    assertWithinPercentage(queryResult + "", firstTotal, queryResult.total, 10);
+                    assertWithinPercentage(queryResult + "", firstTotal, queryResult.total, 12);
                 }
 
                 // Expect to see no traffic when querying for any tag in tagsWithNoTraffic or any
diff --git a/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt b/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt
index 8e98dba..621af23 100644
--- a/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt
@@ -146,6 +146,7 @@
         httpServer.stop()
         handlerThread.threadHandler.post { reader.stop() }
         handlerThread.quitSafely()
+        handlerThread.join()
 
         iface.fileDescriptor.close()
     }
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 2b5c305..9808137 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -19,17 +19,23 @@
 import android.app.compat.CompatChanges
 import android.net.ConnectivityManager
 import android.net.ConnectivityManager.NetworkCallback
+import android.net.InetAddresses.parseNumericAddress
+import android.net.LinkAddress
 import android.net.LinkProperties
+import android.net.LocalSocket
+import android.net.LocalSocketAddress
 import android.net.Network
 import android.net.NetworkAgentConfig
 import android.net.NetworkCapabilities
 import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
 import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
 import android.net.NetworkCapabilities.TRANSPORT_TEST
 import android.net.NetworkRequest
 import android.net.TestNetworkInterface
 import android.net.TestNetworkManager
 import android.net.TestNetworkSpecifier
+import android.net.connectivity.ConnectivityCompatChanges
 import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted
 import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped
 import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.ServiceFound
@@ -40,8 +46,15 @@
 import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.ServiceRegistered
 import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.ServiceUnregistered
 import android.net.cts.NsdManagerTest.NsdRegistrationRecord.RegistrationEvent.UnregistrationFailed
+import android.net.cts.NsdManagerTest.NsdResolveRecord.ResolveEvent.ResolutionStopped
 import android.net.cts.NsdManagerTest.NsdResolveRecord.ResolveEvent.ResolveFailed
 import android.net.cts.NsdManagerTest.NsdResolveRecord.ResolveEvent.ServiceResolved
+import android.net.cts.NsdManagerTest.NsdResolveRecord.ResolveEvent.StopResolutionFailed
+import android.net.cts.NsdManagerTest.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.RegisterCallbackFailed
+import android.net.cts.NsdManagerTest.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdated
+import android.net.cts.NsdManagerTest.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.ServiceUpdatedLost
+import android.net.cts.NsdManagerTest.NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent.UnregisterCallbackSucceeded
+import android.net.cts.util.CtsNetUtils
 import android.net.nsd.NsdManager
 import android.net.nsd.NsdManager.DiscoveryListener
 import android.net.nsd.NsdManager.RegistrationListener
@@ -52,20 +65,53 @@
 import android.os.HandlerThread
 import android.os.Process.myTid
 import android.platform.test.annotations.AppModeFull
+import android.system.ErrnoException
+import android.system.Os
+import android.system.OsConstants.AF_INET6
+import android.system.OsConstants.EADDRNOTAVAIL
+import android.system.OsConstants.ENETUNREACH
+import android.system.OsConstants.IPPROTO_UDP
+import android.system.OsConstants.SOCK_DGRAM
 import android.util.Log
+import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.PollingCheck
+import com.android.compatibility.common.util.PropertyUtil
+import com.android.modules.utils.build.SdkLevel.isAtLeastU
 import com.android.net.module.util.ArrayTrackRecord
 import com.android.net.module.util.TrackRecord
 import com.android.networkstack.apishim.NsdShimImpl
+import com.android.networkstack.apishim.common.NsdShim
 import com.android.testutils.ConnectivityModuleTest
 import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
 import com.android.testutils.TestableNetworkAgent
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkCreated
 import com.android.testutils.TestableNetworkCallback
 import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk30
+import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk33
 import com.android.testutils.runAsShell
 import com.android.testutils.tryTest
 import com.android.testutils.waitForIdle
+import java.io.File
+import java.io.IOException
+import java.net.Inet6Address
+import java.net.InetAddress
+import java.net.NetworkInterface
+import java.net.ServerSocket
+import java.nio.charset.StandardCharsets
+import java.util.Random
+import java.util.concurrent.Executor
+import kotlin.math.min
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
 import org.junit.After
 import org.junit.Assert.assertArrayEquals
 import org.junit.Assert.assertFalse
@@ -75,17 +121,6 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import java.io.File
-import java.net.ServerSocket
-import java.nio.charset.StandardCharsets
-import java.util.Random
-import java.util.concurrent.Executor
-import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
-import kotlin.test.assertNotNull
-import kotlin.test.assertNull
-import kotlin.test.assertTrue
-import kotlin.test.fail
 
 private const val TAG = "NsdManagerTest"
 private const val TIMEOUT_MS = 2000L
@@ -96,11 +131,15 @@
 // tried sequentially
 private const val REGISTRATION_TIMEOUT_MS = 10_000L
 private const val DBG = false
+private const val TEST_PORT = 12345
 
 private val nsdShim = NsdShimImpl.newInstance()
 
 @AppModeFull(reason = "Socket cannot bind in instant app mode")
-@RunWith(AndroidJUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@ConnectivityModuleTest
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
 class NsdManagerTest {
     // Rule used to filter CtsNetTestCasesMaxTargetSdkXX
     @get:Rule
@@ -113,6 +152,7 @@
     private val serviceName = "NsdTest%09d".format(Random().nextInt(1_000_000_000))
     private val serviceType = "_nmt%09d._tcp".format(Random().nextInt(1_000_000_000))
     private val handlerThread = HandlerThread(NsdManagerTest::class.java.simpleName)
+    private val ctsNetUtils by lazy{ CtsNetUtils(context) }
 
     private lateinit var testNetwork1: TestTapNetwork
     private lateinit var testNetwork2: TestTapNetwork
@@ -155,7 +195,8 @@
 
         inline fun <reified V : NsdEvent> expectCallback(timeoutMs: Long = TIMEOUT_MS): V {
             val nextEvent = nextEvents.poll(timeoutMs)
-            assertNotNull(nextEvent, "No callback received after $timeoutMs ms")
+            assertNotNull(nextEvent, "No callback received after $timeoutMs ms, expected " +
+                    "${V::class.java.simpleName}")
             assertTrue(nextEvent is V, "Expected ${V::class.java.simpleName} but got " +
                     nextEvent.javaClass.simpleName)
             return nextEvent
@@ -182,10 +223,10 @@
                 val errorCode: Int
             ) : RegistrationEvent()
 
-            data class ServiceRegistered(override val serviceInfo: NsdServiceInfo)
-                : RegistrationEvent()
-            data class ServiceUnregistered(override val serviceInfo: NsdServiceInfo)
-                : RegistrationEvent()
+            data class ServiceRegistered(override val serviceInfo: NsdServiceInfo) :
+                    RegistrationEvent()
+            data class ServiceUnregistered(override val serviceInfo: NsdServiceInfo) :
+                    RegistrationEvent()
         }
 
         override fun onRegistrationFailed(si: NsdServiceInfo, err: Int) {
@@ -208,11 +249,11 @@
     private class NsdDiscoveryRecord(expectedThreadId: Int? = null) :
             DiscoveryListener, NsdRecord<NsdDiscoveryRecord.DiscoveryEvent>(expectedThreadId) {
         sealed class DiscoveryEvent : NsdEvent {
-            data class StartDiscoveryFailed(val serviceType: String, val errorCode: Int)
-                : DiscoveryEvent()
+            data class StartDiscoveryFailed(val serviceType: String, val errorCode: Int) :
+                    DiscoveryEvent()
 
-            data class StopDiscoveryFailed(val serviceType: String, val errorCode: Int)
-                : DiscoveryEvent()
+            data class StopDiscoveryFailed(val serviceType: String, val errorCode: Int) :
+                    DiscoveryEvent()
 
             data class DiscoveryStarted(val serviceType: String) : DiscoveryEvent()
             data class DiscoveryStopped(val serviceType: String) : DiscoveryEvent()
@@ -246,23 +287,30 @@
 
         fun waitForServiceDiscovered(
             serviceName: String,
+            serviceType: String,
             expectedNetwork: Network? = null
         ): NsdServiceInfo {
-            return expectCallbackEventually<ServiceFound> {
+            val serviceFound = expectCallbackEventually<ServiceFound> {
                 it.serviceInfo.serviceName == serviceName &&
                         (expectedNetwork == null ||
                                 expectedNetwork == nsdShim.getNetwork(it.serviceInfo))
             }.serviceInfo
+            // Discovered service types have a dot at the end
+            assertEquals("$serviceType.", serviceFound.serviceType)
+            return serviceFound
         }
     }
 
     private class NsdResolveRecord : ResolveListener,
             NsdRecord<NsdResolveRecord.ResolveEvent>() {
         sealed class ResolveEvent : NsdEvent {
-            data class ResolveFailed(val serviceInfo: NsdServiceInfo, val errorCode: Int)
-                : ResolveEvent()
+            data class ResolveFailed(val serviceInfo: NsdServiceInfo, val errorCode: Int) :
+                    ResolveEvent()
 
             data class ServiceResolved(val serviceInfo: NsdServiceInfo) : ResolveEvent()
+            data class ResolutionStopped(val serviceInfo: NsdServiceInfo) : ResolveEvent()
+            data class StopResolutionFailed(val serviceInfo: NsdServiceInfo, val errorCode: Int) :
+                    ResolveEvent()
         }
 
         override fun onResolveFailed(si: NsdServiceInfo, err: Int) {
@@ -272,6 +320,41 @@
         override fun onServiceResolved(si: NsdServiceInfo) {
             add(ServiceResolved(si))
         }
+
+        override fun onResolutionStopped(si: NsdServiceInfo) {
+            add(ResolutionStopped(si))
+        }
+
+        override fun onStopResolutionFailed(si: NsdServiceInfo, err: Int) {
+            super.onStopResolutionFailed(si, err)
+            add(StopResolutionFailed(si, err))
+        }
+    }
+
+    private class NsdServiceInfoCallbackRecord : NsdShim.ServiceInfoCallbackShim,
+            NsdRecord<NsdServiceInfoCallbackRecord.ServiceInfoCallbackEvent>() {
+        sealed class ServiceInfoCallbackEvent : NsdEvent {
+            data class RegisterCallbackFailed(val errorCode: Int) : ServiceInfoCallbackEvent()
+            data class ServiceUpdated(val serviceInfo: NsdServiceInfo) : ServiceInfoCallbackEvent()
+            object ServiceUpdatedLost : ServiceInfoCallbackEvent()
+            object UnregisterCallbackSucceeded : ServiceInfoCallbackEvent()
+        }
+
+        override fun onServiceInfoCallbackRegistrationFailed(err: Int) {
+            add(RegisterCallbackFailed(err))
+        }
+
+        override fun onServiceUpdated(si: NsdServiceInfo) {
+            add(ServiceUpdated(si))
+        }
+
+        override fun onServiceLost() {
+            add(ServiceUpdatedLost)
+        }
+
+        override fun onServiceInfoCallbackUnregistered() {
+            add(UnregisterCallbackSucceeded)
+        }
     }
 
     @Before
@@ -298,37 +381,85 @@
                 .build(), cb)
         val agent = registerTestNetworkAgent(iface.interfaceName)
         val network = agent.network ?: fail("Registered agent should have a network")
+
+        cb.eventuallyExpect<LinkPropertiesChanged>(TIMEOUT_MS) {
+            it.lp.linkAddresses.isNotEmpty()
+        }
+
         // The network has no INTERNET capability, so will be marked validated immediately
-        cb.expectAvailableThenValidatedCallbacks(network, TIMEOUT_MS)
+        // It does not matter if validated capabilities come before/after the link addresses change
+        cb.eventuallyExpect<CapabilitiesChanged>(TIMEOUT_MS, from = 0) {
+            it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
+        }
         return TestTapNetwork(iface, cb, agent, network)
     }
 
     private fun registerTestNetworkAgent(ifaceName: String): TestableNetworkAgent {
+        val lp = LinkProperties().apply {
+            interfaceName = ifaceName
+        }
+
         val agent = TestableNetworkAgent(context, handlerThread.looper,
                 NetworkCapabilities().apply {
                     removeCapability(NET_CAPABILITY_TRUSTED)
                     addTransportType(TRANSPORT_TEST)
                     setNetworkSpecifier(TestNetworkSpecifier(ifaceName))
-                },
-                LinkProperties().apply {
-                    interfaceName = ifaceName
-                },
-                NetworkAgentConfig.Builder().build())
-        agent.register()
+                }, lp, NetworkAgentConfig.Builder().build())
+        val network = agent.register()
         agent.markConnected()
+        agent.expectCallback<OnNetworkCreated>()
+
+        // Wait until the link-local address can be used. Address flags are not available without
+        // elevated permissions, so check that bindSocket works.
+        PollingCheck.check("No usable v6 address on interface after $TIMEOUT_MS ms", TIMEOUT_MS) {
+            // To avoid race condition between socket connection succeeding and interface returning
+            // a non-empty address list. Verify that interface returns a non-empty list, before
+            // trying the socket connection.
+            if (NetworkInterface.getByName(ifaceName).interfaceAddresses.isEmpty()) {
+                return@check false
+            }
+
+            val sock = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)
+            tryTest {
+                network.bindSocket(sock)
+                Os.connect(sock, parseNumericAddress("ff02::fb%$ifaceName"), 12345)
+                true
+            }.catch<ErrnoException> {
+                if (it.errno != ENETUNREACH && it.errno != EADDRNOTAVAIL) {
+                    throw it
+                }
+                false
+            } cleanup {
+                Os.close(sock)
+            }
+        }
+
+        lp.setLinkAddresses(NetworkInterface.getByName(ifaceName).interfaceAddresses.map {
+            LinkAddress(it.address, it.networkPrefixLength.toInt())
+        })
+        agent.sendLinkProperties(lp)
         return agent
     }
 
+    private fun makeTestServiceInfo(network: Network? = null) = NsdServiceInfo().also {
+        it.serviceType = serviceType
+        it.serviceName = serviceName
+        it.network = network
+        it.port = TEST_PORT
+    }
+
     @After
     fun tearDown() {
         if (TestUtils.shouldTestTApis()) {
             runAsShell(MANAGE_TEST_NETWORKS) {
-                testNetwork1.close(cm)
-                testNetwork2.close(cm)
+                // Avoid throwing here if initializing failed in setUp
+                if (this::testNetwork1.isInitialized) testNetwork1.close(cm)
+                if (this::testNetwork2.isInitialized) testNetwork2.close(cm)
             }
         }
         handlerThread.waitForIdle(TIMEOUT_MS)
         handlerThread.quitSafely()
+        handlerThread.join()
     }
 
     @Test
@@ -382,6 +513,10 @@
         val registeredInfo = registrationRecord.expectCallback<ServiceRegistered>(
                 REGISTRATION_TIMEOUT_MS).serviceInfo
 
+        // Only service name is included in ServiceRegistered callbacks
+        assertNull(registeredInfo.serviceType)
+        assertEquals(si.serviceName, registeredInfo.serviceName)
+
         val discoveryRecord = NsdDiscoveryRecord()
         // Test discovering without an Executor
         nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
@@ -390,12 +525,15 @@
         discoveryRecord.expectCallback<DiscoveryStarted>()
 
         // Expect a service record to be discovered
-        val foundInfo = discoveryRecord.waitForServiceDiscovered(registeredInfo.serviceName)
+        val foundInfo = discoveryRecord.waitForServiceDiscovered(
+                registeredInfo.serviceName, serviceType)
 
         // Test resolving without an Executor
         val resolveRecord = NsdResolveRecord()
         nsdManager.resolveService(foundInfo, resolveRecord)
         val resolvedService = resolveRecord.expectCallback<ServiceResolved>().serviceInfo
+        assertEquals(".$serviceType", resolvedService.serviceType)
+        assertEquals(registeredInfo.serviceName, resolvedService.serviceName)
 
         // Check Txt attributes
         assertEquals(8, resolvedService.attributes.size)
@@ -410,7 +548,12 @@
         assertTrue(resolvedService.attributes.containsKey("nullBinaryDataAttr"))
         assertNull(resolvedService.attributes["nullBinaryDataAttr"])
         assertTrue(resolvedService.attributes.containsKey("emptyBinaryDataAttr"))
-        assertNull(resolvedService.attributes["emptyBinaryDataAttr"])
+        // TODO: change the check to target SDK U when this is what the code implements
+        if (isAtLeastU()) {
+            assertArrayEquals(byteArrayOf(), resolvedService.attributes["emptyBinaryDataAttr"])
+        } else {
+            assertNull(resolvedService.attributes["emptyBinaryDataAttr"])
+        }
         assertEquals(localPort, resolvedService.port)
 
         // Unregister the service
@@ -418,9 +561,11 @@
         registrationRecord.expectCallback<ServiceUnregistered>()
 
         // Expect a callback for service lost
-        discoveryRecord.expectCallbackEventually<ServiceLost> {
+        val lostCb = discoveryRecord.expectCallbackEventually<ServiceLost> {
             it.serviceInfo.serviceName == serviceName
         }
+        // Lost service types have a dot at the end
+        assertEquals("$serviceType.", lostCb.serviceInfo.serviceType)
 
         // Register service again to see if NsdManager can discover it
         val si2 = NsdServiceInfo()
@@ -434,7 +579,8 @@
 
         // Expect a service record to be discovered (and filter the ones
         // that are unrelated to this test)
-        val foundInfo2 = discoveryRecord.waitForServiceDiscovered(registeredInfo2.serviceName)
+        val foundInfo2 = discoveryRecord.waitForServiceDiscovered(
+                registeredInfo2.serviceName, serviceType)
 
         // Resolve the service
         val resolveRecord2 = NsdResolveRecord()
@@ -471,7 +617,7 @@
                     testNetwork1.network, Executor { it.run() }, discoveryRecord)
 
             val foundInfo = discoveryRecord.waitForServiceDiscovered(
-                    serviceName, testNetwork1.network)
+                    serviceName, serviceType, testNetwork1.network)
             assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo))
 
             // Rewind to ensure the service is not found on the other interface
@@ -518,6 +664,8 @@
 
             val serviceDiscovered = discoveryRecord.expectCallback<ServiceFound>()
             assertEquals(registeredInfo1.serviceName, serviceDiscovered.serviceInfo.serviceName)
+            // Discovered service types have a dot at the end
+            assertEquals("$serviceType.", serviceDiscovered.serviceInfo.serviceType)
             assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceDiscovered.serviceInfo))
 
             // Unregister, then register the service back: it should be lost and found again
@@ -530,6 +678,7 @@
             val registeredInfo2 = registerService(registrationRecord, si, executor)
             val serviceDiscovered2 = discoveryRecord.expectCallback<ServiceFound>()
             assertEquals(registeredInfo2.serviceName, serviceDiscovered2.serviceInfo.serviceName)
+            assertEquals("$serviceType.", serviceDiscovered2.serviceInfo.serviceType)
             assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceDiscovered2.serviceInfo))
 
             // Teardown, then bring back up a network on the test interface: the service should
@@ -545,6 +694,7 @@
             val newNetwork = newAgent.network ?: fail("Registered agent should have a network")
             val serviceDiscovered3 = discoveryRecord.expectCallback<ServiceFound>()
             assertEquals(registeredInfo2.serviceName, serviceDiscovered3.serviceInfo.serviceName)
+            assertEquals("$serviceType.", serviceDiscovered3.serviceInfo.serviceType)
             assertEquals(newNetwork, nsdShim.getNetwork(serviceDiscovered3.serviceInfo))
         } cleanupStep {
             nsdManager.stopServiceDiscovery(discoveryRecord)
@@ -587,6 +737,20 @@
         }
     }
 
+    private fun checkAddressScopeId(iface: TestNetworkInterface, address: List<InetAddress>) {
+        val targetSdkVersion = context.packageManager
+            .getTargetSdkVersion(context.applicationInfo.packageName)
+        if (targetSdkVersion <= Build.VERSION_CODES.TIRAMISU) {
+            return
+        }
+        val ifaceIdx = NetworkInterface.getByName(iface.interfaceName).index
+        address.forEach {
+            if (it is Inet6Address && it.isLinkLocalAddress) {
+                assertEquals(ifaceIdx, it.scopeId)
+            }
+        }
+    }
+
     @Test
     fun testNsdManager_ResolveOnNetwork() {
         // This test requires shims supporting T+ APIs (NsdServiceInfo.network)
@@ -606,12 +770,12 @@
             nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
 
             val foundInfo1 = discoveryRecord.waitForServiceDiscovered(
-                    serviceName, testNetwork1.network)
+                    serviceName, serviceType, testNetwork1.network)
             assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo1))
             // Rewind as the service could be found on each interface in any order
             discoveryRecord.nextEvents.rewind(0)
             val foundInfo2 = discoveryRecord.waitForServiceDiscovered(
-                    serviceName, testNetwork2.network)
+                    serviceName, serviceType, testNetwork2.network)
             assertEquals(testNetwork2.network, nsdShim.getNetwork(foundInfo2))
 
             nsdShim.resolveService(nsdManager, foundInfo1, Executor { it.run() }, resolveRecord)
@@ -622,6 +786,7 @@
                 assertEquals(registeredInfo.serviceName, it.serviceName)
                 assertEquals(si.port, it.port)
                 assertEquals(testNetwork1.network, nsdShim.getNetwork(it))
+                checkAddressScopeId(testNetwork1.iface, it.hostAddresses)
             }
             // TODO: check that MDNS packets are sent only on testNetwork1.
         } cleanupStep {
@@ -655,7 +820,7 @@
                 testNetwork1.network, Executor { it.run() }, discoveryRecord)
             // Expect that service is found on testNetwork1
             val foundInfo = discoveryRecord.waitForServiceDiscovered(
-                serviceName, testNetwork1.network)
+                serviceName, serviceType, testNetwork1.network)
             assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo))
 
             // Discover service on testNetwork2.
@@ -670,7 +835,7 @@
                 null as Network? /* network */, Executor { it.run() }, discoveryRecord3)
             // Expect that service is found on testNetwork1
             val foundInfo3 = discoveryRecord3.waitForServiceDiscovered(
-                    serviceName, testNetwork1.network)
+                    serviceName, serviceType, testNetwork1.network)
             assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo3))
         } cleanupStep {
             nsdManager.stopServiceDiscovery(discoveryRecord2)
@@ -700,7 +865,7 @@
             nsdManager.discoverServices(
                 serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryRecord
             )
-            val foundInfo = discoveryRecord.waitForServiceDiscovered(serviceNames)
+            val foundInfo = discoveryRecord.waitForServiceDiscovered(serviceNames, serviceType)
 
             // Expect that resolving the service name works properly even service name contains
             // non-standard characters.
@@ -715,6 +880,69 @@
         }
     }
 
+    private fun checkConnectSocketToMdnsd(shouldFail: Boolean) {
+        val discoveryRecord = NsdDiscoveryRecord()
+        val localSocket = LocalSocket()
+        tryTest {
+            // Discover any service from NsdManager to enforce NsdService to start the mdnsd.
+            nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
+            discoveryRecord.expectCallback<DiscoveryStarted>()
+
+            // Checks the /dev/socket/mdnsd is created.
+            val socket = File("/dev/socket/mdnsd")
+            val doesSocketExist = PollingCheck.waitFor(
+                TIMEOUT_MS,
+                {
+                    socket.exists()
+                },
+                { doesSocketExist ->
+                    doesSocketExist
+                },
+            )
+
+            // If the socket is not created, then no need to check the access.
+            if (doesSocketExist) {
+                // Create a LocalSocket and try to connect to mdnsd.
+                assertFalse("LocalSocket is connected.", localSocket.isConnected)
+                val address = LocalSocketAddress("mdnsd", LocalSocketAddress.Namespace.RESERVED)
+                if (shouldFail) {
+                    assertFailsWith<IOException>("Expect fail but socket connected") {
+                        localSocket.connect(address)
+                    }
+                } else {
+                    localSocket.connect(address)
+                    assertTrue("LocalSocket is not connected.", localSocket.isConnected)
+                }
+            }
+        } cleanup {
+            localSocket.close()
+            nsdManager.stopServiceDiscovery(discoveryRecord)
+            discoveryRecord.expectCallback<DiscoveryStopped>()
+        }
+    }
+
+    /**
+     * Starting from Android U, the access to the /dev/socket/mdnsd is blocked by the
+     * sepolicy(b/265364111).
+     */
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun testCannotConnectSocketToMdnsd() {
+        val targetSdkVersion = context.packageManager
+                .getTargetSdkVersion(context.applicationInfo.packageName)
+        assumeTrue(targetSdkVersion > Build.VERSION_CODES.TIRAMISU)
+        val firstApiLevel = min(PropertyUtil.getFirstApiLevel(), PropertyUtil.getVendorApiLevel())
+        // The sepolicy is implemented in the vendor image, so the access may not be blocked if
+        // the vendor image is not update to date.
+        assumeTrue(firstApiLevel > Build.VERSION_CODES.TIRAMISU)
+        checkConnectSocketToMdnsd(shouldFail = true)
+    }
+
+    @Test @CtsNetTestCasesMaxTargetSdk33("mdnsd socket is accessible up to target SDK 33")
+    fun testCanConnectSocketToMdnsd() {
+        checkConnectSocketToMdnsd(shouldFail = false)
+    }
+
     @Test @CtsNetTestCasesMaxTargetSdk30("Socket is started with the service up to target SDK 30")
     fun testManagerCreatesLegacySocket() {
         nsdManager // Ensure the lazy-init member is initialized, so NsdManager is created
@@ -736,7 +964,146 @@
         // when the compat change is disabled.
         // Note that before T the compat constant had a different int value.
         assertFalse(CompatChanges.isChangeEnabled(
-                NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER))
+                ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER))
+    }
+
+    @Test
+    fun testStopServiceResolution() {
+        // This test requires shims supporting U+ APIs (NsdManager.stopServiceResolution)
+        assumeTrue(TestUtils.shouldTestUApis())
+
+        val si = NsdServiceInfo()
+        si.serviceType = this@NsdManagerTest.serviceType
+        si.serviceName = this@NsdManagerTest.serviceName
+        si.port = 12345 // Test won't try to connect so port does not matter
+
+        val resolveRecord = NsdResolveRecord()
+        // Try to resolve an unknown service then stop it immediately.
+        // Expected ResolutionStopped callback.
+        nsdShim.resolveService(nsdManager, si, { it.run() }, resolveRecord)
+        nsdShim.stopServiceResolution(nsdManager, resolveRecord)
+        val stoppedCb = resolveRecord.expectCallback<ResolutionStopped>()
+        assertEquals(si.serviceName, stoppedCb.serviceInfo.serviceName)
+        assertEquals(si.serviceType, stoppedCb.serviceInfo.serviceType)
+    }
+
+    @Test
+    fun testRegisterServiceInfoCallback() {
+        // This test requires shims supporting U+ APIs (NsdManager.registerServiceInfoCallback)
+        assumeTrue(TestUtils.shouldTestUApis())
+
+        val lp = cm.getLinkProperties(testNetwork1.network)
+        assertNotNull(lp)
+        val addresses = lp.addresses
+        assertFalse(addresses.isEmpty())
+
+        val si = NsdServiceInfo().apply {
+            serviceType = this@NsdManagerTest.serviceType
+            serviceName = this@NsdManagerTest.serviceName
+            network = testNetwork1.network
+            port = 12345 // Test won't try to connect so port does not matter
+        }
+
+        // Register service on the network
+        val registrationRecord = NsdRegistrationRecord()
+        registerService(registrationRecord, si)
+
+        val discoveryRecord = NsdDiscoveryRecord()
+        val cbRecord = NsdServiceInfoCallbackRecord()
+        tryTest {
+            // Discover service on the network.
+            nsdShim.discoverServices(nsdManager, serviceType, NsdManager.PROTOCOL_DNS_SD,
+                    testNetwork1.network, Executor { it.run() }, discoveryRecord)
+            val foundInfo = discoveryRecord.waitForServiceDiscovered(
+                    serviceName, serviceType, testNetwork1.network)
+
+            // Register service callback and check the addresses are the same as network addresses
+            nsdShim.registerServiceInfoCallback(nsdManager, foundInfo, { it.run() }, cbRecord)
+            val serviceInfoCb = cbRecord.expectCallback<ServiceUpdated>()
+            assertEquals(foundInfo.serviceName, serviceInfoCb.serviceInfo.serviceName)
+            val hostAddresses = serviceInfoCb.serviceInfo.hostAddresses
+            assertEquals(addresses.size, hostAddresses.size)
+            for (hostAddress in hostAddresses) {
+                assertTrue(addresses.contains(hostAddress))
+            }
+            checkAddressScopeId(testNetwork1.iface, serviceInfoCb.serviceInfo.hostAddresses)
+        } cleanupStep {
+            nsdManager.unregisterService(registrationRecord)
+            registrationRecord.expectCallback<ServiceUnregistered>()
+            discoveryRecord.expectCallback<ServiceLost>()
+            cbRecord.expectCallback<ServiceUpdatedLost>()
+        } cleanupStep {
+            // Cancel subscription and check stop callback received.
+            nsdShim.unregisterServiceInfoCallback(nsdManager, cbRecord)
+            cbRecord.expectCallback<UnregisterCallbackSucceeded>()
+        } cleanup {
+            nsdManager.stopServiceDiscovery(discoveryRecord)
+            discoveryRecord.expectCallback<DiscoveryStopped>()
+        }
+    }
+
+    @Test
+    fun testStopServiceResolutionFailedCallback() {
+        // This test requires shims supporting U+ APIs (NsdManager.stopServiceResolution)
+        assumeTrue(TestUtils.shouldTestUApis())
+
+        // It's not possible to make ResolutionListener#onStopResolutionFailed callback sending
+        // because it is only sent in very edge-case scenarios when the legacy implementation is
+        // used, and the legacy implementation is never used in the current AOSP builds. Considering
+        // that this callback isn't expected to be sent at all at the moment, and this is just an
+        // interface with no implementation. To verify this callback, just call
+        // onStopResolutionFailed on the record directly then verify it is received.
+        val resolveRecord = NsdResolveRecord()
+        resolveRecord.onStopResolutionFailed(
+                NsdServiceInfo(), NsdManager.FAILURE_OPERATION_NOT_RUNNING)
+        val failedCb = resolveRecord.expectCallback<StopResolutionFailed>()
+        assertEquals(NsdManager.FAILURE_OPERATION_NOT_RUNNING, failedCb.errorCode)
+    }
+
+    @Test
+    fun testSubtypeAdvertisingAndDiscovery() {
+        val si = makeTestServiceInfo(network = testNetwork1.network)
+        // Test "_type._tcp.local,_subtype" syntax with the registration
+        si.serviceType = si.serviceType + ",_subtype"
+
+        val registrationRecord = NsdRegistrationRecord()
+
+        val baseTypeDiscoveryRecord = NsdDiscoveryRecord()
+        val subtypeDiscoveryRecord = NsdDiscoveryRecord()
+        val otherSubtypeDiscoveryRecord = NsdDiscoveryRecord()
+        tryTest {
+            registerService(registrationRecord, si)
+
+            // Test "_subtype._type._tcp.local" syntax with discovery. Note this is not
+            // "_subtype._sub._type._tcp.local".
+            nsdManager.discoverServices(serviceType,
+                    NsdManager.PROTOCOL_DNS_SD,
+                    testNetwork1.network, Executor { it.run() }, baseTypeDiscoveryRecord)
+            nsdManager.discoverServices("_othersubtype.$serviceType",
+                    NsdManager.PROTOCOL_DNS_SD,
+                    testNetwork1.network, Executor { it.run() }, otherSubtypeDiscoveryRecord)
+            nsdManager.discoverServices("_subtype.$serviceType",
+                    NsdManager.PROTOCOL_DNS_SD,
+                    testNetwork1.network, Executor { it.run() }, subtypeDiscoveryRecord)
+
+            subtypeDiscoveryRecord.waitForServiceDiscovered(
+                    serviceName, serviceType, testNetwork1.network)
+            baseTypeDiscoveryRecord.waitForServiceDiscovered(
+                    serviceName, serviceType, testNetwork1.network)
+            otherSubtypeDiscoveryRecord.expectCallback<DiscoveryStarted>()
+            // The subtype callback was registered later but called, no need for an extra delay
+            otherSubtypeDiscoveryRecord.assertNoCallback(timeoutMs = 0)
+        } cleanupStep {
+            nsdManager.stopServiceDiscovery(baseTypeDiscoveryRecord)
+            nsdManager.stopServiceDiscovery(subtypeDiscoveryRecord)
+            nsdManager.stopServiceDiscovery(otherSubtypeDiscoveryRecord)
+
+            baseTypeDiscoveryRecord.expectCallback<DiscoveryStopped>()
+            subtypeDiscoveryRecord.expectCallback<DiscoveryStopped>()
+            otherSubtypeDiscoveryRecord.expectCallback<DiscoveryStopped>()
+        } cleanup {
+            nsdManager.unregisterService(registrationRecord)
+        }
     }
 
     /**
diff --git a/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java b/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java
index f0c87673..4854901 100644
--- a/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java
@@ -23,12 +23,14 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.app.Instrumentation;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.net.Network;
 import android.net.PacProxyManager;
@@ -150,6 +152,9 @@
     @AppModeFull(reason = "Instant apps can't bind sockets to localhost for a test proxy server")
     @Test
     public void testSetCurrentProxyScriptUrl() throws Exception {
+        // Devices without WebView/JavaScript cannot support PAC proxies
+        assumeTrue(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW));
+
         // Register a PacProxyInstalledListener
         final TestPacProxyInstalledListener listener = new TestPacProxyInstalledListener();
         final Executor executor = (Runnable r) -> r.run();
diff --git a/tests/cts/net/src/android/net/cts/RateLimitTest.java b/tests/cts/net/src/android/net/cts/RateLimitTest.java
index 28cec1a..36b98fc 100644
--- a/tests/cts/net/src/android/net/cts/RateLimitTest.java
+++ b/tests/cts/net/src/android/net/cts/RateLimitTest.java
@@ -301,29 +301,32 @@
     public void testIngressRateLimit_testLimit() throws Exception {
         assumeKernelSupport();
 
+        // These tests are not very precise, especially on lower-end devices.
+        // Add 30% tolerance to reduce test flakiness. Burst size is constant at 128KiB.
+        final double toleranceFactor = 1.3;
+
         // If this value is too low, this test might become flaky because of the burst value that
         // allows to send at a higher data rate for a short period of time. The faster the data rate
         // and the longer the test, the less this test will be affected.
         final long dataLimitInBytesPerSecond = 2_000_000; // 2MB/s
         long resultInBytesPerSecond = runIngressDataRateMeasurement(Duration.ofSeconds(1));
         assertGreaterThan("Failed initial test with rate limit disabled", resultInBytesPerSecond,
-                dataLimitInBytesPerSecond);
+                (long) (dataLimitInBytesPerSecond * toleranceFactor));
 
         // enable rate limit and wait until the tc filter is installed before starting the test.
         ConnectivitySettingsManager.setIngressRateLimitInBytesPerSecond(mContext,
                 dataLimitInBytesPerSecond);
         waitForTcPoliceFilterInstalled(Duration.ofSeconds(1));
 
-        resultInBytesPerSecond = runIngressDataRateMeasurement(Duration.ofSeconds(10));
-        // Add 10% tolerance to reduce test flakiness. Burst size is constant at 128KiB.
+        resultInBytesPerSecond = runIngressDataRateMeasurement(Duration.ofSeconds(15));
         assertLessThan("Failed test with rate limit enabled", resultInBytesPerSecond,
-                (long) (dataLimitInBytesPerSecond * 1.1));
+                (long) (dataLimitInBytesPerSecond * toleranceFactor));
 
         ConnectivitySettingsManager.setIngressRateLimitInBytesPerSecond(mContext, -1);
 
         resultInBytesPerSecond = runIngressDataRateMeasurement(Duration.ofSeconds(1));
         assertGreaterThan("Failed test with rate limit disabled", resultInBytesPerSecond,
-                dataLimitInBytesPerSecond);
+                (long) (dataLimitInBytesPerSecond * toleranceFactor));
     }
 
     @Test
diff --git a/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java b/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
index cbe54f8..1a780a7 100644
--- a/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
+++ b/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
@@ -66,7 +66,6 @@
         InetAddress[] addresses;
         try {
             addresses = InetAddress.getAllByName(TEST_HOST);
-            mTestHostAddress = addresses[0];
         } catch (UnknownHostException uhe) {
             throw new AssertionError(
                 "Unable to test SSLCertificateSocketFactory: cannot resolve " + TEST_HOST, uhe);
@@ -76,10 +75,11 @@
             .map(addr -> new InetSocketAddress(addr, HTTPS_PORT))
             .collect(Collectors.toList());
 
-        // Find the local IP address which will be used to connect to TEST_HOST.
+        // Find the local and remote IP addresses which will be used to connect to TEST_HOST.
         try {
             Socket testSocket = new Socket(TEST_HOST, HTTPS_PORT);
             mLocalAddress = testSocket.getLocalAddress();
+            mTestHostAddress = testSocket.getInetAddress();
             testSocket.close();
         } catch (IOException ioe) {
             throw new AssertionError(""
diff --git a/tests/cts/net/src/android/net/cts/TunUtils.java b/tests/cts/net/src/android/net/cts/TunUtils.java
index 0377160..268d8d2 100644
--- a/tests/cts/net/src/android/net/cts/TunUtils.java
+++ b/tests/cts/net/src/android/net/cts/TunUtils.java
@@ -22,7 +22,6 @@
 import static android.net.cts.PacketUtils.UDP_HDRLEN;
 import static android.system.OsConstants.IPPROTO_UDP;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import android.os.ParcelFileDescriptor;
@@ -32,6 +31,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -140,10 +140,8 @@
     public byte[] awaitEspPacketNoPlaintext(
             int spi, byte[] plaintext, boolean useEncap, int expectedPacketSize) throws Exception {
         final byte[] espPkt = awaitPacket(
-                (pkt) -> isEspFailIfSpecifiedPlaintextFound(pkt, spi, useEncap, plaintext));
-
-        // Validate packet size
-        assertEquals(expectedPacketSize, espPkt.length);
+            (pkt) -> expectedPacketSize == pkt.length
+                    && isEspFailIfSpecifiedPlaintextFound(pkt, spi, useEncap, plaintext));
 
         return espPkt; // We've found the packet we're looking for.
     }
@@ -153,11 +151,11 @@
     }
 
     private static boolean isSpiEqual(byte[] pkt, int espOffset, int spi) {
-        // Check SPI byte by byte.
-        return pkt[espOffset] == (byte) ((spi >>> 24) & 0xff)
-                && pkt[espOffset + 1] == (byte) ((spi >>> 16) & 0xff)
-                && pkt[espOffset + 2] == (byte) ((spi >>> 8) & 0xff)
-                && pkt[espOffset + 3] == (byte) (spi & 0xff);
+        ByteBuffer buffer = ByteBuffer.wrap(pkt);
+        buffer.get(new byte[espOffset]); // Skip IP, UDP header
+        int actualSpi = buffer.getInt();
+
+        return actualSpi == spi;
     }
 
     /**
@@ -180,8 +178,13 @@
 
     private static boolean isEsp(byte[] pkt, int spi, boolean encap) {
         if (isIpv6(pkt)) {
-            // IPv6 UDP encap not supported by kernels; assume non-encap.
-            return pkt[IP6_PROTO_OFFSET] == IPPROTO_ESP && isSpiEqual(pkt, IP6_HDRLEN, spi);
+            if (encap) {
+                return pkt[IP6_PROTO_OFFSET] == IPPROTO_UDP
+                        && isSpiEqual(pkt, IP6_HDRLEN + UDP_HDRLEN, spi);
+            } else {
+                return pkt[IP6_PROTO_OFFSET] == IPPROTO_ESP && isSpiEqual(pkt, IP6_HDRLEN, spi);
+            }
+
         } else {
             // Use default IPv4 header length (assuming no options)
             if (encap) {
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 3f2ff9d..ce789fc 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
@@ -57,6 +57,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
@@ -68,6 +70,8 @@
 import java.io.OutputStream;
 import java.net.InetSocketAddress;
 import java.net.Socket;
+import java.util.ArrayList;
+import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -75,6 +79,13 @@
 
 public final class CtsNetUtils {
     private static final String TAG = CtsNetUtils.class.getSimpleName();
+
+    // Redefine this flag here so that IPsec code shipped in a mainline module can build on old
+    // platforms before FEATURE_IPSEC_TUNNEL_MIGRATION API is released.
+    // TODO: b/275378783 Remove this flag and use the platform API when it is available.
+    private static final String FEATURE_IPSEC_TUNNEL_MIGRATION =
+            "android.software.ipsec_tunnel_migration";
+
     private static final int SOCKET_TIMEOUT_MS = 2000;
     private static final int PRIVATE_DNS_PROBE_MS = 1_000;
 
@@ -115,6 +126,11 @@
                 || getFirstApiLevel() >= Build.VERSION_CODES.Q;
     }
 
+    /** Checks if FEATURE_IPSEC_TUNNEL_MIGRATION is enabled on the device */
+    public boolean hasIpsecTunnelMigrateFeature() {
+        return mContext.getPackageManager().hasSystemFeature(FEATURE_IPSEC_TUNNEL_MIGRATION);
+    }
+
     /**
      * Sets the given appop using shell commands
      *
@@ -170,7 +186,7 @@
         }
     }
 
-    private Network expectNetworkIsSystemDefault(Network network)
+    public Network expectNetworkIsSystemDefault(Network network)
             throws Exception {
         final CompletableFuture<Network> future = new CompletableFuture();
         final NetworkCallback cb = new NetworkCallback() {
@@ -410,7 +426,7 @@
                 .build();
     }
 
-    private void testHttpRequest(Socket s) throws IOException {
+    public void testHttpRequest(Socket s) throws IOException {
         OutputStream out = s.getOutputStream();
         InputStream in = s.getInputStream();
 
@@ -418,7 +434,9 @@
         byte[] responseBytes = new byte[4096];
         out.write(requestBytes);
         in.read(responseBytes);
-        assertTrue(new String(responseBytes, "UTF-8").startsWith("HTTP/1.0 204 No Content\r\n"));
+        final String response = new String(responseBytes, "UTF-8");
+        assertTrue("Received unexpected response: " + response,
+                response.startsWith("HTTP/1.0 204 No Content\r\n"));
     }
 
     private Socket getBoundSocket(Network network, String host, int port) throws IOException {
@@ -494,17 +512,18 @@
      * @throws InterruptedException If the thread is interrupted.
      */
     public void awaitPrivateDnsSetting(@NonNull String msg, @NonNull Network network,
-            @NonNull String server, boolean requiresValidatedServer) throws InterruptedException {
+            @Nullable String server, boolean requiresValidatedServer) throws InterruptedException {
         final CountDownLatch latch = new CountDownLatch(1);
         final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
-        NetworkCallback callback = new NetworkCallback() {
+        final NetworkCallback callback = new NetworkCallback() {
             @Override
             public void onLinkPropertiesChanged(Network n, LinkProperties lp) {
                 Log.i(TAG, "Link properties of network " + n + " changed to " + lp);
                 if (requiresValidatedServer && lp.getValidatedPrivateDnsServers().isEmpty()) {
                     return;
                 }
-                if (network.equals(n) && server.equals(lp.getPrivateDnsServerName())) {
+                Log.i(TAG, "Set private DNS server to " + server);
+                if (network.equals(n) && Objects.equals(server, lp.getPrivateDnsServerName())) {
                     latch.countDown();
                 }
             }
@@ -527,6 +546,27 @@
     }
 
     /**
+     * Get all testable Networks with internet capability.
+     */
+    public Network[] getTestableNetworks() {
+        final ArrayList<Network> testableNetworks = new ArrayList<Network>();
+        for (Network network : mCm.getAllNetworks()) {
+            final NetworkCapabilities nc = mCm.getNetworkCapabilities(network);
+            if (nc != null
+                    && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                    && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+                testableNetworks.add(network);
+            }
+        }
+
+        assertTrue("This test requires that at least one public Internet-providing"
+                        + " network be connected. Please ensure that the device is connected to"
+                        + " a network.",
+                testableNetworks.size() >= 1);
+        return testableNetworks.toArray(new Network[0]);
+    }
+
+    /**
      * Receiver that captures the last connectivity change's network type and state. Recognizes
      * both {@code CONNECTIVITY_ACTION} and {@code NETWORK_CALLBACK_ACTION} intents.
      */
diff --git a/tests/cts/net/util/java/android/net/cts/util/IkeSessionTestUtils.java b/tests/cts/net/util/java/android/net/cts/util/IkeSessionTestUtils.java
index 11eb466..25534b8 100644
--- a/tests/cts/net/util/java/android/net/cts/util/IkeSessionTestUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/IkeSessionTestUtils.java
@@ -42,8 +42,9 @@
 public class IkeSessionTestUtils {
     private static final String TEST_SERVER_ADDR_V4 = "192.0.2.2";
     private static final String TEST_SERVER_ADDR_V6 = "2001:db8::2";
-    private static final String TEST_IDENTITY = "client.cts.android.com";
+    public static final String TEST_IDENTITY = "client.cts.android.com";
     private static final byte[] TEST_PSK = "ikeAndroidPsk".getBytes();
+    public static final int TEST_KEEPALIVE_TIMEOUT_UNSET = -1;
     public static final IkeSessionParams IKE_PARAMS_V4 = getTestIkeSessionParams(false);
     public static final IkeSessionParams IKE_PARAMS_V6 = getTestIkeSessionParams(true);
 
@@ -63,17 +64,26 @@
 
     public static IkeSessionParams getTestIkeSessionParams(boolean testIpv6,
             IkeIdentification identification) {
+        return getTestIkeSessionParams(testIpv6, identification, TEST_KEEPALIVE_TIMEOUT_UNSET);
+    }
+
+    public static IkeSessionParams getTestIkeSessionParams(boolean testIpv6,
+            IkeIdentification identification, int keepaliveTimer) {
         final String testServer = testIpv6 ? TEST_SERVER_ADDR_V6 : TEST_SERVER_ADDR_V4;
         final InetAddress addr = InetAddresses.parseNumericAddress(testServer);
         final IkeSessionParams.Builder ikeOptionsBuilder =
                 new IkeSessionParams.Builder()
                         .setServerHostname(testServer)
-                        .setLocalIdentification(new IkeFqdnIdentification(TEST_IDENTITY))
+                        .setLocalIdentification(identification)
                         .setRemoteIdentification(testIpv6
                                 ? new IkeIpv6AddrIdentification((Inet6Address) addr)
                                 : new IkeIpv4AddrIdentification((Inet4Address) addr))
                         .setAuthPsk(TEST_PSK)
+
                         .addSaProposal(getIkeSaProposals());
+        if (keepaliveTimer != TEST_KEEPALIVE_TIMEOUT_UNSET) {
+            ikeOptionsBuilder.setNattKeepAliveDelaySeconds(keepaliveTimer);
+        }
 
         return ikeOptionsBuilder.build();
     }
diff --git a/tests/cts/netpermission/internetpermission/AndroidManifest.xml b/tests/cts/netpermission/internetpermission/AndroidManifest.xml
index 45ef5bd..ae7de3f 100644
--- a/tests/cts/netpermission/internetpermission/AndroidManifest.xml
+++ b/tests/cts/netpermission/internetpermission/AndroidManifest.xml
@@ -43,8 +43,6 @@
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
          android:targetPackage="android.networkpermission.internetpermission.cts"
          android:label="CTS tests for INTERNET permissions">
-        <meta-data android:name="listener"
-             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/cts/netpermission/updatestatspermission/AndroidManifest.xml b/tests/cts/netpermission/updatestatspermission/AndroidManifest.xml
index 6babe8f..8a7e3f7 100644
--- a/tests/cts/netpermission/updatestatspermission/AndroidManifest.xml
+++ b/tests/cts/netpermission/updatestatspermission/AndroidManifest.xml
@@ -51,8 +51,6 @@
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
          android:targetPackage="android.networkpermission.updatestatspermission.cts"
          android:label="CTS tests for UPDATE_DEVICE_STATS permissions">
-        <meta-data android:name="listener"
-             android:value="com.android.cts.runner.CtsTestRunListener"/>
     </instrumentation>
 
 </manifest>
diff --git a/tests/cts/tethering/Android.bp b/tests/cts/tethering/Android.bp
index 42949a4..4284f56 100644
--- a/tests/cts/tethering/Android.bp
+++ b/tests/cts/tethering/Android.bp
@@ -46,6 +46,7 @@
 
     // Change to system current when TetheringManager move to bootclass path.
     platform_apis: true,
+    host_required: ["net-tests-utils-host-common"],
 }
 
 // Tethering CTS tests that target the latest released SDK. These tests can be installed on release
diff --git a/tests/cts/tethering/AndroidManifest.xml b/tests/cts/tethering/AndroidManifest.xml
index 911dbf2..bad722b 100644
--- a/tests/cts/tethering/AndroidManifest.xml
+++ b/tests/cts/tethering/AndroidManifest.xml
@@ -27,8 +27,6 @@
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.tethering.cts"
                      android:label="CTS tests of android.tethering">
-        <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
     </instrumentation>
 
 </manifest>
diff --git a/tests/cts/tethering/AndroidTestTemplate.xml b/tests/cts/tethering/AndroidTestTemplate.xml
index 491b004..9b33afe 100644
--- a/tests/cts/tethering/AndroidTestTemplate.xml
+++ b/tests/cts/tethering/AndroidTestTemplate.xml
@@ -25,6 +25,8 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="{MODULE}.apk" />
     </target_preparer>
+    <target_preparer class="com.android.testutils.ConnectivityTestTargetPreparer">
+    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.tethering.cts" />
     </test>
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index e3d80a0..12919ae 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -21,7 +21,10 @@
 
 android_test {
     name: "FrameworksNetIntegrationTests",
-    defaults: ["framework-connectivity-internal-test-defaults"],
+    defaults: [
+        "framework-connectivity-internal-test-defaults",
+        "NetworkStackApiShimSettingsForCurrentBranch",
+    ],
     platform_apis: true,
     certificate: "platform",
     srcs: [
@@ -33,6 +36,13 @@
         "ServiceConnectivityResources",
     ],
     static_libs: [
+        // It does not matter if NetworkStackApiStableLib or NetworkStackApiCurrentLib is used here,
+        // since the shims for the branch are already included via
+        // NetworkStackApiShimSettingsForCurrentBranch, and will be used in priority as they are
+        // first in the classpath.
+        // If the wrong shims are used for some reason, tests that use newer APIs fail.
+        // TODO: have NetworkStackApiStableLib link dynamically against the shims to remove this
+        // order-dependent setup.
         "NetworkStackApiStableLib",
         "androidx.test.ext.junit",
         "frameworks-net-integration-testutils",
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 26b058d..67e1296 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -25,7 +25,6 @@
 import android.content.ServiceConnection
 import android.content.res.Resources
 import android.net.ConnectivityManager
-import android.net.ConnectivityResources
 import android.net.IDnsResolver
 import android.net.INetd
 import android.net.LinkProperties
@@ -37,7 +36,6 @@
 import android.net.TestNetworkStackClient
 import android.net.Uri
 import android.net.metrics.IpConnectivityLog
-import com.android.server.connectivity.MultinetworkPolicyTracker
 import android.os.ConditionVariable
 import android.os.IBinder
 import android.os.SystemConfigManager
@@ -51,9 +49,15 @@
 import com.android.server.ConnectivityService
 import com.android.server.NetworkAgentWrapper
 import com.android.server.TestNetIdManager
+import com.android.server.connectivity.ConnectivityResources
 import com.android.server.connectivity.MockableSystemProperties
+import com.android.server.connectivity.MultinetworkPolicyTracker
 import com.android.server.connectivity.ProxyTracker
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
 import com.android.testutils.TestableNetworkCallback
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.test.fail
 import org.junit.After
 import org.junit.Before
 import org.junit.BeforeClass
@@ -72,11 +76,6 @@
 import org.mockito.Mockito.spy
 import org.mockito.MockitoAnnotations
 import org.mockito.Spy
-import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertNotNull
-import kotlin.test.assertTrue
-import kotlin.test.fail
 
 const val SERVICE_BIND_TIMEOUT_MS = 5_000L
 const val TEST_TIMEOUT_MS = 10_000L
@@ -216,8 +215,8 @@
                     inv.getArgument(2),
                     object : MultinetworkPolicyTracker.Dependencies() {
                         override fun getResourcesForActiveSubId(
-                            connResources: ConnectivityResources,
-                            activeSubId: Int
+                                connResources: ConnectivityResources,
+                                activeSubId: Int
                         ) = resources
                     })
         }.`when`(deps).makeMultinetworkPolicyTracker(any(), any(), any())
@@ -289,15 +288,16 @@
 
         testCb.expectAvailableCallbacks(na.network, validated = false, tmt = TEST_TIMEOUT_MS)
 
-        val capportData = testCb.expectLinkPropertiesThat(na, TEST_TIMEOUT_MS) {
-            it.captivePortalData != null
+        val capportData = testCb.expect<LinkPropertiesChanged>(na, TEST_TIMEOUT_MS) {
+            it.lp.captivePortalData != null
         }.lp.captivePortalData
-        assertNotNull(capportData)
         assertTrue(capportData.isCaptive)
         assertEquals(Uri.parse("https://login.capport.android.com"), capportData.userPortalUrl)
         assertEquals(Uri.parse("https://venueinfo.capport.android.com"), capportData.venueInfoUrl)
 
-        val nc = testCb.expectCapabilitiesWith(NET_CAPABILITY_CAPTIVE_PORTAL, na, TEST_TIMEOUT_MS)
-        assertFalse(nc.hasCapability(NET_CAPABILITY_VALIDATED))
+        testCb.expectCaps(na, TEST_TIMEOUT_MS) {
+            it.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL) &&
+                    !it.hasCapability(NET_CAPABILITY_VALIDATED)
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index aa5654a..c294e7b 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -20,8 +20,6 @@
 #include <set>
 #include <string>
 
-#include <android/api-level.h>
-#include <android-base/properties.h>
 #include <android-modules-utils/sdk_level.h>
 #include <bpf/BpfUtils.h>
 
@@ -45,7 +43,7 @@
 class BpfExistenceTest : public ::testing::Test {
 };
 
-// Part of Android R platform, but mainlined in S
+// Part of Android R platform (for 4.9+), but mainlined in S
 static const set<string> PLATFORM_ONLY_IN_R = {
     PLATFORM "map_offload_tether_ingress_map",
     PLATFORM "map_offload_tether_limit_map",
@@ -128,17 +126,18 @@
     SHARED "prog_dscpPolicy_schedcls_set_dscp_ether",
 };
 
-void addAll(set<string>* a, const set<string>& b) {
-    a->insert(b.begin(), b.end());
+static void addAll(set<string>& a, const set<string>& b) {
+    a.insert(b.begin(), b.end());
 }
 
-#define DO_EXPECT(B, V) do { \
-    if (B) addAll(expected, (V)); else addAll(unexpected, (V)); \
-} while (0)
+#define DO_EXPECT(B, V) addAll((B) ? mustExist : mustNotExist, (V))
 
-void getFileLists(set<string>* expected, set<string>* unexpected) {
-    unexpected->clear();
-    expected->clear();
+TEST_F(BpfExistenceTest, TestPrograms) {
+    // Only unconfined root is guaranteed to be able to access everything in /sys/fs/bpf.
+    ASSERT_EQ(0, getuid()) << "This test must run as root.";
+
+    set<string> mustExist;
+    set<string> mustNotExist;
 
     // We do not actually check the platform P/Q (netd) and Q (clatd) things
     // and only verify the mainline module relevant R+ offload maps & progs.
@@ -147,24 +146,23 @@
     // and not to test the platform itself, which may have been modified by vendor or oems,
     // so we should only test for the removal of stuff that was mainline'd,
     // and for the presence of mainline stuff.
-    DO_EXPECT(IsAtLeastR() && !IsAtLeastS(), PLATFORM_ONLY_IN_R);
 
+    // R can potentially run on pre-4.9 kernel non-eBPF capable devices.
+    DO_EXPECT(IsAtLeastR() && !IsAtLeastS() && isAtLeastKernelVersion(4, 9, 0), PLATFORM_ONLY_IN_R);
+
+    // S requires Linux Kernel 4.9+ and thus requires eBPF support.
     DO_EXPECT(IsAtLeastS(), MAINLINE_FOR_S_PLUS);
     DO_EXPECT(IsAtLeastS() && isAtLeastKernelVersion(5, 10, 0), MAINLINE_FOR_S_5_10_PLUS);
 
     // Nothing added or removed in SCv2.
 
+    // T still only requires Linux Kernel 4.9+.
     DO_EXPECT(IsAtLeastT(), MAINLINE_FOR_T_PLUS);
     DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(4, 14, 0), MAINLINE_FOR_T_4_14_PLUS);
     DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(5, 4, 0), MAINLINE_FOR_T_5_4_PLUS);
     DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(5, 15, 0), MAINLINE_FOR_T_5_15_PLUS);
-}
 
-void checkFiles() {
-    set<string> mustExist;
-    set<string> mustNotExist;
-
-    getFileLists(&mustExist, &mustNotExist);
+    // U requires Linux Kernel 4.14+, but nothing (as yet) added or removed in U.
 
     for (const auto& file : mustExist) {
         EXPECT_EQ(0, access(file.c_str(), R_OK)) << file << " does not exist";
@@ -178,19 +176,3 @@
         }
     }
 }
-
-TEST_F(BpfExistenceTest, TestPrograms) {
-    SKIP_IF_BPF_NOT_SUPPORTED;
-
-    // Pre-flight check to ensure test has been updated.
-    uint64_t buildVersionSdk = android_get_device_api_level();
-    ASSERT_NE(0, buildVersionSdk) << "Unable to determine device SDK version";
-    if (buildVersionSdk > __ANDROID_API_T__ && buildVersionSdk != __ANDROID_API_FUTURE__) {
-            FAIL() << "Unknown OS version " << buildVersionSdk << ", please update this test";
-    }
-
-    // Only unconfined root is guaranteed to be able to access everything in /sys/fs/bpf.
-    ASSERT_EQ(0, getuid()) << "This test must run as root.";
-
-    checkFiles();
-}
diff --git a/tests/native/utilities/firewall.h b/tests/native/utilities/firewall.h
index 185559b..1e7e987 100644
--- a/tests/native/utilities/firewall.h
+++ b/tests/native/utilities/firewall.h
@@ -19,7 +19,7 @@
 
 #include <android-base/thread_annotations.h>
 #include <bpf/BpfMap.h>
-#include <bpf_shared.h>
+#include "netd.h"
 
 using android::base::Result;
 using android::bpf::BpfMap;
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 8ed735a..8b286a0 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -26,7 +26,6 @@
         "libandroid_net_frameworktests_util_jni",
         "libbase",
         "libbinder",
-        "libbpf_bcc",
         "libc++",
         "libcrypto",
         "libcutils",
@@ -69,11 +68,11 @@
         "java/com/android/server/VpnManagerServiceTest.java",
         "java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java",
         "java/com/android/server/connectivity/IpConnectivityMetricsTest.java",
+        "java/com/android/server/connectivity/MetricsTestUtil.java",
         "java/com/android/server/connectivity/MultipathPolicyTrackerTest.java",
         "java/com/android/server/connectivity/NetdEventListenerServiceTest.java",
         "java/com/android/server/connectivity/VpnTest.java",
         "java/com/android/server/net/ipmemorystore/*.java",
-        "java/com/android/server/connectivity/mdns/**/*.java",
     ]
 }
 
@@ -115,6 +114,7 @@
         "service-connectivity-pre-jarjar",
         "service-connectivity-tiramisu-pre-jarjar",
         "services.core-vpn",
+        "testables",
         "cts-net-utils"
     ],
     libs: [
@@ -136,6 +136,37 @@
     visibility: ["//packages/modules/Connectivity/tests:__subpackages__"],
 }
 
+genrule {
+    name: "frameworks-net-tests-jarjar-rules",
+    defaults: ["jarjar-rules-combine-defaults"],
+    srcs: [
+        ":frameworks-net-tests-lib-jarjar-gen",
+        // This is necessary because the tests use framework-connectivity-internal-test-defaults,
+        // which require the user to use connectivity jarjar rules.
+        ":connectivity-jarjar-rules",
+    ],
+    out: ["frameworks-net-tests-jarjar-rules.txt"],
+    visibility: ["//packages/modules/Connectivity/tests:__subpackages__"],
+}
+
+java_genrule {
+    name: "frameworks-net-tests-lib-jarjar-gen",
+    tool_files: [
+        ":FrameworksNetTestsLib{.jar}",
+        "jarjar-excludes.txt",
+    ],
+    tools: [
+        "jarjar-rules-generator",
+    ],
+    out: ["frameworks-net-tests-lib-jarjar-rules.txt"],
+    cmd: "$(location jarjar-rules-generator) " +
+        "$(location :FrameworksNetTestsLib{.jar}) " +
+        "--prefix android.net.connectivity " +
+        "--excludes $(location jarjar-excludes.txt) " +
+        "--output $(out)",
+    visibility: ["//visibility:private"],
+}
+
 android_test {
     name: "FrameworksNetTests",
     enabled: enable_frameworks_net_tests,
@@ -143,12 +174,11 @@
         "FrameworksNetTestsDefaults",
         "FrameworksNetTests-jni-defaults",
     ],
-    jarjar_rules: ":connectivity-jarjar-rules",
+    jarjar_rules: ":frameworks-net-tests-jarjar-rules",
     test_suites: ["device-tests"],
     static_libs: [
         "services.core",
         "services.net",
-        "service-mdns",
     ],
     jni_libs: [
         "libandroid_net_connectivity_com_android_net_module_util_jni",
diff --git a/tests/unit/AndroidManifest.xml b/tests/unit/AndroidManifest.xml
index 5bac2dd..5d4bdf7 100644
--- a/tests/unit/AndroidManifest.xml
+++ b/tests/unit/AndroidManifest.xml
@@ -63,7 +63,7 @@
         <uses-library android:name="android.test.runner" />
         <uses-library android:name="android.net.ipsec.ike" />
         <activity
-            android:name="com.android.server.connectivity.NetworkNotificationManagerTest$TestDialogActivity"/>
+            android:name="android.net.connectivity.com.android.server.connectivity.NetworkNotificationManagerTest$TestDialogActivity"/>
     </application>
 
     <instrumentation
diff --git a/tests/unit/jarjar-excludes.txt b/tests/unit/jarjar-excludes.txt
new file mode 100644
index 0000000..d2022bf
--- /dev/null
+++ b/tests/unit/jarjar-excludes.txt
@@ -0,0 +1,27 @@
+# Exclude some test prefixes, otherwise the classes reference below can't find
+# them after jarjared.
+android\.compat\..+
+androidx\.test\..+
+com\.android\.frameworks\.tests\..+
+com\.android\.testutils\..+
+com\.android\.dx\.mockito\..+
+com\.android\.internal\.compat\..+
+com\.android\.internal\.org\.bouncycastle\..+
+kotlin\.test\..+
+kotlin\.reflect\..+
+org\.junit\..+
+org\.mockito\..+
+
+# Auto-jarjar-gen can't handle kotlin object expression, exclude the tests which use
+# object expressions.
+#
+# For example: Illegal class access:
+# 'android.net.connectivity.com.android.net.module.util.TrackRecordTest' attempting to access
+# 'com.android.networkstack.tethering.util.TRTInterpreter' (declaration of
+# 'android.net.connectivity.com.android.net.module.util.TrackRecordTest' ...
+#
+# In coverage test, TRTInterpreter don't be jarjar'ed to
+# android.net.connectivity* by frameworks-net-tests-jarjar-rules instead it is
+# jarjar'ed by follow up TetheringTestsJarJarRules.
+# TODO(b/269259216): remove this after fixing Auto-jarjar-gen.
+com\.android\.net\.module\.util\.TrackRecord.*
diff --git a/tests/unit/java/android/net/Ikev2VpnProfileTest.java b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
index 3b68120..e12e961 100644
--- a/tests/unit/java/android/net/Ikev2VpnProfileTest.java
+++ b/tests/unit/java/android/net/Ikev2VpnProfileTest.java
@@ -492,6 +492,29 @@
     }
 
     @Test
+    public void testAutomaticNattAndIpVersionConversionIsLossless() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+        builder.setAutomaticNattKeepaliveTimerEnabled(true);
+        builder.setAutomaticIpVersionSelectionEnabled(true);
+
+        builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+        final Ikev2VpnProfile ikeProfile = builder.build();
+
+        assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+    }
+
+    @Test
+    public void testAutomaticNattAndIpVersionDefaults() throws Exception {
+        final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+        builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+        final Ikev2VpnProfile ikeProfile = builder.build();
+
+        assertEquals(false, ikeProfile.isAutomaticNattKeepaliveTimerEnabled());
+        assertEquals(false, ikeProfile.isAutomaticIpVersionSelectionEnabled());
+    }
+
+    @Test
     public void testEquals() throws Exception {
         // Verify building without IkeTunnelConnectionParams
         final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
diff --git a/tests/unit/java/android/net/IpSecTransformTest.java b/tests/unit/java/android/net/IpSecTransformTest.java
index c1bd719..8bc1bbd 100644
--- a/tests/unit/java/android/net/IpSecTransformTest.java
+++ b/tests/unit/java/android/net/IpSecTransformTest.java
@@ -18,22 +18,92 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.content.Context;
 import android.os.Build;
+import android.test.mock.MockContext;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.IpSecService;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.net.InetAddress;
+
 /** Unit tests for {@link IpSecTransform}. */
 @SmallTest
 @RunWith(DevSdkIgnoreRunner.class)
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
 public class IpSecTransformTest {
+    @Rule public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
+    private static final int DROID_SPI = 0xD1201D;
+    private static final int TEST_RESOURCE_ID = 0x1234;
+
+    private static final InetAddress SRC_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.200");
+    private static final InetAddress DST_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.201");
+    private static final InetAddress SRC_ADDRESS_V6 =
+            InetAddresses.parseNumericAddress("2001:db8::200");
+    private static final InetAddress DST_ADDRESS_V6 =
+            InetAddresses.parseNumericAddress("2001:db8::201");
+
+    private MockContext mMockContext;
+    private IpSecService mMockIpSecService;
+    private IpSecManager mIpSecManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockIpSecService = mock(IpSecService.class);
+        mIpSecManager = new IpSecManager(mock(Context.class) /* unused */, mMockIpSecService);
+
+        // Set up mMockContext since IpSecTransform needs an IpSecManager instance and a non-null
+        // package name to create transform
+        mMockContext =
+                new MockContext() {
+                    @Override
+                    public String getSystemServiceName(Class<?> serviceClass) {
+                        if (serviceClass.equals(IpSecManager.class)) {
+                            return Context.IPSEC_SERVICE;
+                        }
+                        throw new UnsupportedOperationException();
+                    }
+
+                    @Override
+                    public Object getSystemService(String name) {
+                        if (name.equals(Context.IPSEC_SERVICE)) {
+                            return mIpSecManager;
+                        }
+                        throw new UnsupportedOperationException();
+                    }
+
+                    @Override
+                    public String getOpPackageName() {
+                        return "fooPackage";
+                    }
+                };
+
+        final IpSecSpiResponse spiResp =
+                new IpSecSpiResponse(IpSecManager.Status.OK, TEST_RESOURCE_ID, DROID_SPI);
+        when(mMockIpSecService.allocateSecurityParameterIndex(any(), anyInt(), any()))
+                .thenReturn(spiResp);
+
+        final IpSecTransformResponse transformResp =
+                new IpSecTransformResponse(IpSecManager.Status.OK, TEST_RESOURCE_ID);
+        when(mMockIpSecService.createTransform(any(), any(), any())).thenReturn(transformResp);
+    }
 
     @Test
     public void testCreateTransformCopiesConfig() {
@@ -64,4 +134,34 @@
 
         assertEquals(config1, config2);
     }
+
+    private IpSecTransform buildTestTransform() throws Exception {
+        final IpSecManager.SecurityParameterIndex spi =
+                mIpSecManager.allocateSecurityParameterIndex(DST_ADDRESS);
+        return new IpSecTransform.Builder(mMockContext).buildTunnelModeTransform(SRC_ADDRESS, spi);
+    }
+
+    @Test
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testStartTransformMigration() throws Exception {
+        mIpSecManager.startTunnelModeTransformMigration(
+                buildTestTransform(), SRC_ADDRESS_V6, DST_ADDRESS_V6);
+        verify(mMockIpSecService)
+                .migrateTransform(
+                        anyInt(),
+                        eq(SRC_ADDRESS_V6.getHostAddress()),
+                        eq(DST_ADDRESS_V6.getHostAddress()),
+                        any());
+    }
+
+    @Test
+    @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.TIRAMISU)
+    public void testStartTransformMigrationOnSdkBeforeU() throws Exception {
+        try {
+            mIpSecManager.startTunnelModeTransformMigration(
+                    buildTestTransform(), SRC_ADDRESS_V6, DST_ADDRESS_V6);
+            fail("Expect to fail since migration is not supported before U");
+        } catch (UnsupportedOperationException expected) {
+        }
+    }
 }
diff --git a/tests/unit/java/android/net/NetworkStatsAccessTest.java b/tests/unit/java/android/net/NetworkStatsAccessTest.java
index a74056b..8b86211 100644
--- a/tests/unit/java/android/net/NetworkStatsAccessTest.java
+++ b/tests/unit/java/android/net/NetworkStatsAccessTest.java
@@ -78,6 +78,7 @@
         setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
         setHasReadHistoryPermission(false);
         setHasNetworkStackPermission(false);
+        setHasMainlineNetworkStackPermission(false);
     }
 
     @After
@@ -154,6 +155,10 @@
         setHasNetworkStackPermission(false);
         assertEquals(NetworkStatsAccess.Level.DEFAULT,
                 NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
+
+        setHasMainlineNetworkStackPermission(true);
+        assertEquals(NetworkStatsAccess.Level.DEVICE,
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
     }
 
     private void setHasCarrierPrivileges(boolean hasPrivileges) {
@@ -189,4 +194,10 @@
                 TEST_PID, TEST_UID)).thenReturn(hasPermission ? PackageManager.PERMISSION_GRANTED
                 : PackageManager.PERMISSION_DENIED);
     }
+
+    private void setHasMainlineNetworkStackPermission(boolean hasPermission) {
+        when(mContext.checkPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+                TEST_PID, TEST_UID)).thenReturn(hasPermission ? PackageManager.PERMISSION_GRANTED
+                : PackageManager.PERMISSION_DENIED);
+    }
 }
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
index 3cf0228..2f6c76b 100644
--- a/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -34,10 +34,8 @@
 import android.net.NetworkStats.ROAMING_ALL
 import android.net.NetworkTemplate.MATCH_CARRIER
 import android.net.NetworkTemplate.MATCH_MOBILE
-import android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD
 import android.net.NetworkTemplate.MATCH_TEST
 import android.net.NetworkTemplate.MATCH_WIFI
-import android.net.NetworkTemplate.MATCH_WIFI_WILDCARD
 import android.net.NetworkTemplate.NETWORK_TYPE_ALL
 import android.net.NetworkTemplate.OEM_MANAGED_ALL
 import android.net.NetworkTemplate.OEM_MANAGED_NO
@@ -49,20 +47,20 @@
 import android.net.wifi.WifiInfo
 import android.os.Build
 import android.telephony.TelephonyManager
-import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
 import com.android.testutils.assertParcelSane
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertTrue
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
-import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertNotEquals
-import kotlin.test.assertTrue
 
 private const val TEST_IMSI1 = "imsi1"
 private const val TEST_IMSI2 = "imsi2"
@@ -73,6 +71,8 @@
 @RunWith(DevSdkIgnoreRunner::class)
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
 class NetworkTemplateTest {
+    @get:Rule
+    val ignoreRule = DevSdkIgnoreRule()
     private val mockContext = mock(Context::class.java)
     private val mockWifiInfo = mock(WifiInfo::class.java)
 
@@ -133,10 +133,17 @@
                 mockContext, buildWifiNetworkState(null, TEST_WIFI_KEY1), true, 0)
         val identWifiImsi1Key1 = buildNetworkIdentity(
                 mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_WIFI_KEY1), true, 0)
+        // This identity with a null wifiNetworkKey is to test matchesWifiNetworkKey won't crash
+        // the system when a null wifiNetworkKey is provided, which happens because of a bug in wifi
+        // and it should still match the wifi wildcard template. See b/266598304.
+        val identWifiNullKey = buildNetworkIdentity(
+                mockContext, buildWifiNetworkState(null /* subscriberId */,
+                null /* wifiNetworkKey */), true, 0)
 
         templateWifiWildcard.assertDoesNotMatch(identMobileImsi1)
         templateWifiWildcard.assertMatches(identWifiImsiNullKey1)
         templateWifiWildcard.assertMatches(identWifiImsi1Key1)
+        templateWifiWildcard.assertMatches(identWifiNullKey)
     }
 
     @Test
@@ -151,6 +158,9 @@
                 .setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build()
         val templateWifiKeyAllImsi1 = NetworkTemplate.Builder(MATCH_WIFI)
                 .setSubscriberIds(setOf(TEST_IMSI1)).build()
+        val templateNullWifiKey = NetworkTemplate(MATCH_WIFI,
+                emptyArray<String>() /* subscriberIds */, arrayOf(null) /* wifiNetworkKeys */,
+                METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL)
 
         val identMobile1 = buildNetworkIdentity(mockContext, buildMobileNetworkState(TEST_IMSI1),
                 false, TelephonyManager.NETWORK_TYPE_UMTS)
@@ -162,6 +172,12 @@
                 mockContext, buildWifiNetworkState(TEST_IMSI2, TEST_WIFI_KEY1), true, 0)
         val identWifiImsi1Key2 = buildNetworkIdentity(
                 mockContext, buildWifiNetworkState(TEST_IMSI1, TEST_WIFI_KEY2), true, 0)
+        // This identity with a null wifiNetworkKey is to test the matchesWifiNetworkKey won't crash
+        // the system when a null wifiNetworkKey is provided, which would happen in some unknown
+        // cases, see b/266598304.
+        val identWifiNullKey = buildNetworkIdentity(
+                mockContext, buildWifiNetworkState(null /* subscriberId */,
+                null /* wifiNetworkKey */), true, 0)
 
         // Verify that template with WiFi Network Key only matches any subscriberId and
         // specific WiFi Network Key.
@@ -194,6 +210,24 @@
         templateWifiKeyAllImsi1.assertMatches(identWifiImsi1Key1)
         templateWifiKeyAllImsi1.assertDoesNotMatch(identWifiImsi2Key1)
         templateWifiKeyAllImsi1.assertMatches(identWifiImsi1Key2)
+
+        // Test a network identity with null wifiNetworkKey won't crash.
+        // It should not match a template with wifiNetworkKeys is non-null.
+        // Also, it should not match a template with wifiNetworkKeys that contains null.
+        templateWifiKey1.assertDoesNotMatch(identWifiNullKey)
+        templateNullWifiKey.assertDoesNotMatch(identWifiNullKey)
+    }
+
+    @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun testBuildTemplateMobileAll_nullSubscriberId() {
+        val templateMobileAllWithNullImsi = buildTemplateMobileAll(null)
+        val setWithNull = HashSet<String?>().apply {
+            add(null)
+        }
+        val templateFromBuilder = NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES)
+            .setSubscriberIds(setWithNull).build()
+        assertEquals(templateFromBuilder, templateMobileAllWithNullImsi)
     }
 
     @Test
@@ -232,7 +266,6 @@
         val templateMobileWildcard = buildTemplateMobileWildcard()
         val templateMobileNullImsiWithRatType = NetworkTemplate.Builder(MATCH_MOBILE)
                 .setRatType(TelephonyManager.NETWORK_TYPE_UMTS).build()
-
         val mobileImsi1 = buildMobileNetworkState(TEST_IMSI1)
         val identMobile1 = buildNetworkIdentity(mockContext, mobileImsi1,
                 false /* defaultNetwork */, TelephonyManager.NETWORK_TYPE_UMTS)
@@ -447,20 +480,48 @@
     }
 
     @Test
+    fun testEquals() {
+        val templateImsi1 = NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES)
+                .setSubscriberIds(setOf(TEST_IMSI1)).setRatType(TelephonyManager.NETWORK_TYPE_UMTS)
+                .build()
+        val dupTemplateImsi1 = NetworkTemplate(MATCH_MOBILE, arrayOf(TEST_IMSI1),
+                emptyArray<String>(), METERED_YES, ROAMING_ALL, DEFAULT_NETWORK_ALL,
+                TelephonyManager.NETWORK_TYPE_UMTS, OEM_MANAGED_ALL)
+        val templateImsi2 = NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES)
+                .setSubscriberIds(setOf(TEST_IMSI2)).setRatType(TelephonyManager.NETWORK_TYPE_UMTS)
+                .build()
+
+        assertEquals(templateImsi1, dupTemplateImsi1)
+        assertEquals(dupTemplateImsi1, templateImsi1)
+        assertNotEquals(templateImsi1, templateImsi2)
+
+        val templateWifiKey1 = NetworkTemplate.Builder(MATCH_WIFI)
+                .setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build()
+        val dupTemplateWifiKey1 = NetworkTemplate(MATCH_WIFI, emptyArray<String>(),
+                arrayOf(TEST_WIFI_KEY1), METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL,
+                NETWORK_TYPE_ALL, OEM_MANAGED_ALL)
+        val templateWifiKey2 = NetworkTemplate.Builder(MATCH_WIFI)
+                .setWifiNetworkKeys(setOf(TEST_WIFI_KEY2)).build()
+
+        assertEquals(templateWifiKey1, dupTemplateWifiKey1)
+        assertEquals(dupTemplateWifiKey1, templateWifiKey1)
+        assertNotEquals(templateWifiKey1, templateWifiKey2)
+    }
+
+    @Test
     fun testParcelUnparcel() {
-        val templateMobile = NetworkTemplate(MATCH_MOBILE, TEST_IMSI1, emptyArray<String>(),
+        val templateMobile = NetworkTemplate(MATCH_MOBILE, arrayOf(TEST_IMSI1),
                 emptyArray<String>(), METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL,
-                TelephonyManager.NETWORK_TYPE_LTE, OEM_MANAGED_ALL,
-                SUBSCRIBER_ID_MATCH_RULE_EXACT)
-        val templateWifi = NetworkTemplate(MATCH_WIFI, null, emptyArray<String>(),
+                TelephonyManager.NETWORK_TYPE_LTE, OEM_MANAGED_ALL)
+        val templateWifi = NetworkTemplate(MATCH_WIFI, emptyArray<String>(),
                 arrayOf(TEST_WIFI_KEY1), METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, 0,
-                OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
-        val templateOem = NetworkTemplate(MATCH_MOBILE, null, emptyArray<String>(),
+                OEM_MANAGED_ALL)
+        val templateOem = NetworkTemplate(MATCH_MOBILE, emptyArray<String>(),
                 emptyArray<String>(), METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, 0,
-                OEM_MANAGED_YES, SUBSCRIBER_ID_MATCH_RULE_EXACT)
-        assertParcelSane(templateMobile, 10)
-        assertParcelSane(templateWifi, 10)
-        assertParcelSane(templateOem, 10)
+                OEM_MANAGED_YES)
+        assertParcelSane(templateMobile, 8)
+        assertParcelSane(templateWifi, 8)
+        assertParcelSane(templateOem, 8)
     }
 
     // Verify NETWORK_TYPE_* constants in NetworkTemplate do not conflict with
@@ -493,13 +554,14 @@
      * @param matchType A match rule from {@code NetworkTemplate.MATCH_*} corresponding to the
      *         networkType.
      * @param subscriberId To be populated with {@code TEST_IMSI*} only if networkType is
-     *         {@code TYPE_MOBILE}. May be left as null when matchType is
-     *         {@link NetworkTemplate.MATCH_MOBILE_WILDCARD}.
-     * @param templateWifiKey Top be populated with {@code TEST_WIFI_KEY*} only if networkType is
-     *         {@code TYPE_WIFI}. May be left as null when matchType is
-     *         {@link NetworkTemplate.MATCH_WIFI_WILDCARD}.
-     * @param identWifiKey If networkType is {@code TYPE_WIFI}, this value must *NOT* be null. Provide
-     *         one of {@code TEST_WIFI_KEY*}.
+     *         {@code TYPE_MOBILE}. Note that {@code MATCH_MOBILE} with an empty subscriberId list
+     *         will match any subscriber ID.
+     * @param templateWifiKey To be populated with {@code TEST_WIFI_KEY*} only if networkType is
+     *         {@code TYPE_WIFI}. Note that {@code MATCH_WIFI} with both an empty subscriberId list
+     *         and an empty wifiNetworkKey list will match any subscriber ID and/or any wifi network
+     *         key.
+     * @param identWifiKey If networkType is {@code TYPE_WIFI}, this value must *NOT* be null.
+     *         Provide one of {@code TEST_WIFI_KEY*}.
      */
     private fun matchOemManagedIdent(
         networkType: Int,
@@ -509,17 +571,17 @@
         identWifiKey: String? = null
     ) {
         val oemManagedStates = arrayOf(OEM_NONE, OEM_PAID, OEM_PRIVATE, OEM_PAID or OEM_PRIVATE)
-        val matchSubscriberIds = arrayOf(subscriberId)
-        val matchWifiNetworkKeys = arrayOf(templateWifiKey)
+        val matchSubscriberIds =
+                if (subscriberId == null) emptyArray<String>() else arrayOf(subscriberId)
+        val matchWifiNetworkKeys =
+                if (templateWifiKey == null) emptyArray<String>() else arrayOf(templateWifiKey)
 
-        val templateOemYes = NetworkTemplate(matchType, subscriberId, matchSubscriberIds,
+        val templateOemYes = NetworkTemplate(matchType, matchSubscriberIds,
                 matchWifiNetworkKeys, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_YES,
-                SUBSCRIBER_ID_MATCH_RULE_EXACT)
-        val templateOemAll = NetworkTemplate(matchType, subscriberId, matchSubscriberIds,
+                DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_YES)
+        val templateOemAll = NetworkTemplate(matchType, matchSubscriberIds,
                 matchWifiNetworkKeys, METERED_ALL, ROAMING_ALL,
-                DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
-                SUBSCRIBER_ID_MATCH_RULE_EXACT)
+                DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, OEM_MANAGED_ALL)
 
         for (identityOemManagedState in oemManagedStates) {
             val ident = buildNetworkIdentity(mockContext, buildNetworkState(networkType,
@@ -528,10 +590,9 @@
 
             // Create a template with each OEM managed type and match it against the NetworkIdentity
             for (templateOemManagedState in oemManagedStates) {
-                val template = NetworkTemplate(matchType, subscriberId, matchSubscriberIds,
+                val template = NetworkTemplate(matchType, matchSubscriberIds,
                         matchWifiNetworkKeys, METERED_ALL, ROAMING_ALL,
-                        DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, templateOemManagedState,
-                        SUBSCRIBER_ID_MATCH_RULE_EXACT)
+                        DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL, templateOemManagedState)
                 if (identityOemManagedState == templateOemManagedState) {
                     template.assertMatches(ident)
                 } else {
@@ -552,11 +613,10 @@
     @Test
     fun testOemManagedMatchesIdent() {
         matchOemManagedIdent(TYPE_MOBILE, MATCH_MOBILE, subscriberId = TEST_IMSI1)
-        matchOemManagedIdent(TYPE_MOBILE, MATCH_MOBILE_WILDCARD)
+        matchOemManagedIdent(TYPE_MOBILE, MATCH_MOBILE)
         matchOemManagedIdent(TYPE_WIFI, MATCH_WIFI, templateWifiKey = TEST_WIFI_KEY1,
                 identWifiKey = TEST_WIFI_KEY1)
-        matchOemManagedIdent(TYPE_WIFI, MATCH_WIFI_WILDCARD,
-                identWifiKey = TEST_WIFI_KEY1)
+        matchOemManagedIdent(TYPE_WIFI, MATCH_WIFI, identWifiKey = TEST_WIFI_KEY1)
     }
 
     @Test
diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java
index 8a4932b..0965193 100644
--- a/tests/unit/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java
@@ -21,6 +21,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -32,6 +33,7 @@
 
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.Context;
+import android.net.connectivity.ConnectivityCompatChanges;
 import android.os.Build;
 
 import androidx.test.filters.SmallTest;
@@ -72,79 +74,79 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        doReturn(mServiceConn).when(mService).connect(any());
+        doReturn(mServiceConn).when(mService).connect(any(), anyBoolean());
         mManager = new NsdManager(mContext, mService);
         final ArgumentCaptor<INsdManagerCallback> cbCaptor = ArgumentCaptor.forClass(
                 INsdManagerCallback.class);
-        verify(mService).connect(cbCaptor.capture());
+        verify(mService).connect(cbCaptor.capture(), anyBoolean());
         mCallback = cbCaptor.getValue();
     }
 
     @Test
-    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @EnableCompatChanges(ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testResolveServiceS() throws Exception {
         verify(mServiceConn, never()).startDaemon();
         doTestResolveService();
     }
 
     @Test
-    @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @DisableCompatChanges(ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testResolveServicePreS() throws Exception {
         verify(mServiceConn).startDaemon();
         doTestResolveService();
     }
 
     @Test
-    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @EnableCompatChanges(ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testDiscoverServiceS() throws Exception {
         verify(mServiceConn, never()).startDaemon();
         doTestDiscoverService();
     }
 
     @Test
-    @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @DisableCompatChanges(ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testDiscoverServicePreS() throws Exception {
         verify(mServiceConn).startDaemon();
         doTestDiscoverService();
     }
 
     @Test
-    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @EnableCompatChanges(ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testParallelResolveServiceS() throws Exception {
         verify(mServiceConn, never()).startDaemon();
         doTestParallelResolveService();
     }
 
     @Test
-    @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @DisableCompatChanges(ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testParallelResolveServicePreS() throws Exception {
         verify(mServiceConn).startDaemon();
         doTestParallelResolveService();
     }
 
     @Test
-    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @EnableCompatChanges(ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testInvalidCallsS() throws Exception {
         verify(mServiceConn, never()).startDaemon();
         doTestInvalidCalls();
     }
 
     @Test
-    @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @DisableCompatChanges(ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testInvalidCallsPreS() throws Exception {
         verify(mServiceConn).startDaemon();
         doTestInvalidCalls();
     }
 
     @Test
-    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @EnableCompatChanges(ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testRegisterServiceS() throws Exception {
         verify(mServiceConn, never()).startDaemon();
         doTestRegisterService();
     }
 
     @Test
-    @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @DisableCompatChanges(ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
     public void testRegisterServicePreS() throws Exception {
         verify(mServiceConn).startDaemon();
         doTestRegisterService();
diff --git a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
index 64355ed..9ce0693 100644
--- a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
+++ b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.net.InetAddresses;
 import android.net.Network;
 import android.os.Build;
 import android.os.Bundle;
@@ -38,6 +39,7 @@
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 
 @RunWith(DevSdkIgnoreRunner.class)
@@ -45,6 +47,8 @@
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
 public class NsdServiceInfoTest {
 
+    private static final InetAddress IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1");
+    private static final InetAddress IPV6_ADDRESS = InetAddresses.parseNumericAddress("2001:db8::");
     public final static InetAddress LOCALHOST;
     static {
         // Because test.
@@ -124,6 +128,7 @@
         fullInfo.setServiceType("_kitten._tcp");
         fullInfo.setPort(4242);
         fullInfo.setHost(LOCALHOST);
+        fullInfo.setHostAddresses(List.of(IPV4_ADDRESS));
         fullInfo.setNetwork(new Network(123));
         fullInfo.setInterfaceIndex(456);
         checkParcelable(fullInfo);
@@ -139,6 +144,7 @@
         attributedInfo.setServiceType("_kitten._tcp");
         attributedInfo.setPort(4242);
         attributedInfo.setHost(LOCALHOST);
+        fullInfo.setHostAddresses(List.of(IPV6_ADDRESS, IPV4_ADDRESS));
         attributedInfo.setAttribute("color", "pink");
         attributedInfo.setAttribute("sound", (new String("にゃあ")).getBytes("UTF-8"));
         attributedInfo.setAttribute("adorable", (String) null);
diff --git a/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
index 9203f8f..cb3a315 100644
--- a/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
+++ b/tests/unit/java/android/net/util/KeepaliveUtilsTest.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.content.res.Resources
-import android.net.ConnectivityResources
 import android.net.NetworkCapabilities
 import android.net.NetworkCapabilities.MAX_TRANSPORT
 import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
@@ -27,7 +26,9 @@
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.os.Build
 import androidx.test.filters.SmallTest
-import com.android.internal.R
+import com.android.connectivity.resources.R
+import com.android.server.connectivity.ConnectivityResources
+import com.android.server.connectivity.KeepaliveResourceUtil
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
 import org.junit.After
@@ -37,7 +38,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito.any
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.mock
 
@@ -53,14 +53,9 @@
 class KeepaliveUtilsTest {
 
     // Prepare mocked context with given resource strings.
-    private fun getMockedContextWithStringArrayRes(
-        id: Int,
-        name: String,
-        res: Array<out String?>?
-    ): Context {
+    private fun getMockedContextWithStringArrayRes(id: Int, res: Array<out String?>?): Context {
         val mockRes = mock(Resources::class.java)
         doReturn(res).`when`(mockRes).getStringArray(eq(id))
-        doReturn(id).`when`(mockRes).getIdentifier(eq(name), any(), any())
 
         return mock(Context::class.java).apply {
             doReturn(mockRes).`when`(this).getResources()
@@ -79,10 +74,10 @@
             try {
                 val mockContext = getMockedContextWithStringArrayRes(
                         R.array.config_networkSupportedKeepaliveCount,
-                        "config_networkSupportedKeepaliveCount", res)
-                KeepaliveUtils.getSupportedKeepalives(mockContext)
+                        res)
+                KeepaliveResourceUtil.getSupportedKeepalives(mockContext)
                 fail("Expected KeepaliveDeviceConfigurationException")
-            } catch (expected: KeepaliveUtils.KeepaliveDeviceConfigurationException) {
+            } catch (expected: KeepaliveResourceUtil.KeepaliveDeviceConfigurationException) {
             }
         }
 
@@ -104,12 +99,12 @@
 
         // Check valid customization generates expected array.
         val validRes = arrayOf("0,3", "1,0", "4,4")
-        val expectedValidRes = intArrayOf(3, 0, 0, 0, 4, 0, 0, 0, 0)
+        val expectedValidRes = intArrayOf(3, 0, 0, 0, 4, 0, 0, 0, 0, 0)
 
         val mockContext = getMockedContextWithStringArrayRes(
                 R.array.config_networkSupportedKeepaliveCount,
-                "config_networkSupportedKeepaliveCount", validRes)
-        val actual = KeepaliveUtils.getSupportedKeepalives(mockContext)
+                validRes)
+        val actual = KeepaliveResourceUtil.getSupportedKeepalives(mockContext)
         assertArrayEquals(expectedValidRes, actual)
     }
 
diff --git a/tests/unit/java/com/android/internal/net/VpnProfileTest.java b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
index 0a6d2f2..b2dff2e 100644
--- a/tests/unit/java/com/android/internal/net/VpnProfileTest.java
+++ b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
@@ -20,6 +20,7 @@
 import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V4;
 
 import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastU;
 import static com.android.testutils.ParcelUtils.assertParcelSane;
 
 import static org.junit.Assert.assertEquals;
@@ -55,6 +56,9 @@
     private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24;
     private static final int ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE = 25;
     private static final int ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION = 26;
+    private static final int ENCODED_INDEX_IKE_TUN_CONN_PARAMS = 27;
+    private static final int ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED = 28;
+    private static final int ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED = 29;
 
     @Test
     public void testDefaults() throws Exception {
@@ -85,12 +89,15 @@
         assertFalse(p.isRestrictedToTestNetworks);
         assertFalse(p.excludeLocalRoutes);
         assertFalse(p.requiresInternetValidation);
+        assertFalse(p.automaticNattKeepaliveTimerEnabled);
+        assertFalse(p.automaticIpVersionSelectionEnabled);
     }
 
     private VpnProfile getSampleIkev2Profile(String key) {
         final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
                 false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
-                null /* ikeTunConnParams */);
+                null /* ikeTunConnParams */, true /* mAutomaticNattKeepaliveTimerEnabled */,
+                true /* automaticIpVersionSelectionEnabled */);
 
         p.name = "foo";
         p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
@@ -128,7 +135,9 @@
     private VpnProfile getSampleIkev2ProfileWithIkeTunConnParams(String key) {
         final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
                 false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
-                new IkeTunnelConnectionParams(IKE_PARAMS_V4, CHILD_PARAMS));
+                new IkeTunnelConnectionParams(IKE_PARAMS_V4, CHILD_PARAMS),
+                true /* mAutomaticNattKeepaliveTimerEnabled */,
+                true /* automaticIpVersionSelectionEnabled */);
 
         p.name = "foo";
         p.server = "bar";
@@ -166,7 +175,11 @@
 
     @Test
     public void testParcelUnparcel() {
-        if (isAtLeastT()) {
+        if (isAtLeastU()) {
+            // automaticNattKeepaliveTimerEnabled, automaticIpVersionSelectionEnabled added in U.
+            assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 28);
+            assertParcelSane(getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY), 28);
+        } else if (isAtLeastT()) {
             // excludeLocalRoutes, requiresPlatformValidation were added in T.
             assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 26);
             assertParcelSane(getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY), 26);
@@ -221,16 +234,28 @@
                         ENCODED_INDEX_AUTH_PARAMS_INLINE,
                         ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
                         ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
-                        ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION /* missingIndices */);
+                        ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION,
+                        ENCODED_INDEX_IKE_TUN_CONN_PARAMS,
+                        ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED,
+                        ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED
+                        /* missingIndices */);
 
         assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()));
     }
 
+    private String getEncodedDecodedIkev2ProfileWithtooFewValues() {
+        return getEncodedDecodedIkev2ProfileMissingValues(
+                ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
+                ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
+                ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION,
+                ENCODED_INDEX_IKE_TUN_CONN_PARAMS,
+                ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED,
+                ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED /* missingIndices */);
+    }
+
     @Test
     public void testEncodeDecodeMissingIsRestrictedToTestNetworks() {
-        final String tooFewValues =
-                getEncodedDecodedIkev2ProfileMissingValues(
-                        ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS /* missingIndices */);
+        final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
 
         // Verify decoding without isRestrictedToTestNetworks defaults to false
         final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
@@ -239,10 +264,7 @@
 
     @Test
     public void testEncodeDecodeMissingExcludeLocalRoutes() {
-        final String tooFewValues =
-                getEncodedDecodedIkev2ProfileMissingValues(
-                        ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
-                        ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION /* missingIndices */);
+        final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
 
         // Verify decoding without excludeLocalRoutes defaults to false
         final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
@@ -251,9 +273,7 @@
 
     @Test
     public void testEncodeDecodeMissingRequiresValidation() {
-        final String tooFewValues =
-                getEncodedDecodedIkev2ProfileMissingValues(
-                        ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION /* missingIndices */);
+        final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
 
         // Verify decoding without requiresValidation defaults to false
         final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
@@ -261,6 +281,24 @@
     }
 
     @Test
+    public void testEncodeDecodeMissingAutomaticNattKeepaliveTimerEnabled() {
+        final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+        // Verify decoding without automaticNattKeepaliveTimerEnabled defaults to false
+        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+        assertFalse(decoded.automaticNattKeepaliveTimerEnabled);
+    }
+
+    @Test
+    public void testEncodeDecodeMissingAutomaticIpVersionSelectionEnabled() {
+        final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+        // Verify decoding without automaticIpVersionSelectionEnabled defaults to false
+        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+        assertFalse(decoded.automaticIpVersionSelectionEnabled);
+    }
+
+    @Test
     public void testEncodeDecodeLoginsNotSaved() {
         final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
         profile.saveLogin = false;
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index 0e17cd7..19fa41d 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -66,6 +66,7 @@
 import android.os.Build;
 import android.os.ServiceSpecificException;
 import android.system.ErrnoException;
+import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 
 import androidx.test.filters.SmallTest;
@@ -690,6 +691,80 @@
                 mBpfNetMaps.setUidRule(FIREWALL_CHAIN_DOZABLE, TEST_UID, FIREWALL_RULE_ALLOW));
     }
 
+    private void doTestGetUidRule(final List<Integer> enableChains) throws Exception {
+        mUidOwnerMap.updateEntry(new S32(TEST_UID), new UidOwnerValue(0, getMatch(enableChains)));
+
+        for (final int chain: FIREWALL_CHAINS) {
+            final String testCase = "EnabledChains: " + enableChains + " CheckedChain: " + chain;
+            if (enableChains.contains(chain)) {
+                final int expectedRule = mBpfNetMaps.isFirewallAllowList(chain)
+                        ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY;
+                assertEquals(testCase, expectedRule, mBpfNetMaps.getUidRule(chain, TEST_UID));
+            } else {
+                final int expectedRule = mBpfNetMaps.isFirewallAllowList(chain)
+                        ? FIREWALL_RULE_DENY : FIREWALL_RULE_ALLOW;
+                assertEquals(testCase, expectedRule, mBpfNetMaps.getUidRule(chain, TEST_UID));
+            }
+        }
+    }
+
+    private void doTestGetUidRule(final int enableChain) throws Exception {
+        doTestGetUidRule(List.of(enableChain));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testGetUidRule() throws Exception {
+        doTestGetUidRule(FIREWALL_CHAIN_DOZABLE);
+        doTestGetUidRule(FIREWALL_CHAIN_STANDBY);
+        doTestGetUidRule(FIREWALL_CHAIN_POWERSAVE);
+        doTestGetUidRule(FIREWALL_CHAIN_RESTRICTED);
+        doTestGetUidRule(FIREWALL_CHAIN_LOW_POWER_STANDBY);
+        doTestGetUidRule(FIREWALL_CHAIN_OEM_DENY_1);
+        doTestGetUidRule(FIREWALL_CHAIN_OEM_DENY_2);
+        doTestGetUidRule(FIREWALL_CHAIN_OEM_DENY_3);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testGetUidRuleMultipleChainEnabled() throws Exception {
+        doTestGetUidRule(List.of(
+                FIREWALL_CHAIN_DOZABLE,
+                FIREWALL_CHAIN_STANDBY));
+        doTestGetUidRule(List.of(
+                FIREWALL_CHAIN_DOZABLE,
+                FIREWALL_CHAIN_STANDBY,
+                FIREWALL_CHAIN_POWERSAVE,
+                FIREWALL_CHAIN_RESTRICTED));
+        doTestGetUidRule(FIREWALL_CHAINS);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testGetUidRuleNoEntry() throws Exception {
+        mUidOwnerMap.clear();
+        for (final int chain: FIREWALL_CHAINS) {
+            final int expectedRule = mBpfNetMaps.isFirewallAllowList(chain)
+                    ? FIREWALL_RULE_DENY : FIREWALL_RULE_ALLOW;
+            assertEquals(expectedRule, mBpfNetMaps.getUidRule(chain, TEST_UID));
+        }
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    public void testGetUidRuleInvalidChain() {
+        final Class<ServiceSpecificException> expected = ServiceSpecificException.class;
+        assertThrows(expected, () -> mBpfNetMaps.getUidRule(-1 /* childChain */, TEST_UID));
+        assertThrows(expected, () -> mBpfNetMaps.getUidRule(1000 /* childChain */, TEST_UID));
+    }
+
+    @Test
+    @IgnoreAfter(Build.VERSION_CODES.S_V2)
+    public void testGetUidRuleBeforeT() {
+        assertThrows(UnsupportedOperationException.class,
+                () -> mBpfNetMaps.getUidRule(FIREWALL_CHAIN_DOZABLE, TEST_UID));
+    }
+
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.S_V2)
     public void testReplaceUidChain() throws Exception {
@@ -1077,4 +1152,33 @@
         mCookieTagMap.updateEntry(new CookieTagMapKey(123), new CookieTagMapValue(456, 0x789));
         assertDumpContains(getDump(), "cookie=123 tag=0x789 uid=456");
     }
+
+    @Test
+    public void testGetUids() throws ErrnoException {
+        final int uid0 = TEST_UIDS[0];
+        final int uid1 = TEST_UIDS[1];
+        final long match0 = DOZABLE_MATCH | POWERSAVE_MATCH;
+        final long match1 = DOZABLE_MATCH | STANDBY_MATCH;
+        mUidOwnerMap.updateEntry(new S32(uid0), new UidOwnerValue(NULL_IIF, match0));
+        mUidOwnerMap.updateEntry(new S32(uid1), new UidOwnerValue(NULL_IIF, match1));
+
+        assertEquals(new ArraySet<>(List.of(uid0, uid1)),
+                mBpfNetMaps.getUidsWithAllowRuleOnAllowListChain(FIREWALL_CHAIN_DOZABLE));
+        assertEquals(new ArraySet<>(List.of(uid0)),
+                mBpfNetMaps.getUidsWithAllowRuleOnAllowListChain(FIREWALL_CHAIN_POWERSAVE));
+
+        assertEquals(new ArraySet<>(List.of(uid1)),
+                mBpfNetMaps.getUidsWithDenyRuleOnDenyListChain(FIREWALL_CHAIN_STANDBY));
+        assertEquals(new ArraySet<>(),
+                mBpfNetMaps.getUidsWithDenyRuleOnDenyListChain(FIREWALL_CHAIN_OEM_DENY_1));
+    }
+
+    @Test
+    public void testGetUidsIllegalArgument() {
+        final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
+        assertThrows(expected,
+                () -> mBpfNetMaps.getUidsWithDenyRuleOnDenyListChain(FIREWALL_CHAIN_DOZABLE));
+        assertThrows(expected,
+                () -> mBpfNetMaps.getUidsWithAllowRuleOnAllowListChain(FIREWALL_CHAIN_OEM_DENY_1));
+    }
 }
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 16f7039..904e4bd 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -27,8 +27,11 @@
 import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
 import static android.Manifest.permission.NETWORK_FACTORY;
 import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.NETWORK_SETUP_WIZARD;
 import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD;
+import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
+import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.content.Intent.ACTION_PACKAGE_ADDED;
 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
@@ -64,6 +67,7 @@
 import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
+import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING;
 import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
 import static android.net.ConnectivityManager.TYPE_ETHERNET;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
@@ -137,6 +141,7 @@
 import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST;
 import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY;
 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.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_ADDED;
 import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_REMOVED;
@@ -145,11 +150,14 @@
 import static android.os.Process.INVALID_UID;
 import static android.system.OsConstants.IPPROTO_TCP;
 
+import static com.android.server.ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION;
 import static com.android.server.ConnectivityService.MAX_NETWORK_REQUESTS_PER_SYSTEM_UID;
 import static com.android.server.ConnectivityService.PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED;
 import static com.android.server.ConnectivityService.PREFERENCE_ORDER_OEM;
 import static com.android.server.ConnectivityService.PREFERENCE_ORDER_PROFILE;
 import static com.android.server.ConnectivityService.PREFERENCE_ORDER_VPN;
+import static com.android.server.ConnectivityService.createDeliveryGroupKeyForConnectivityAction;
+import static com.android.server.ConnectivityService.makeNflogPrefix;
 import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType;
 import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackRegister;
 import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister;
@@ -159,6 +167,7 @@
 import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 import static com.android.testutils.FunctionalUtils.ignoreExceptions;
+import static com.android.testutils.HandlerUtils.visibleOnHandlerThread;
 import static com.android.testutils.HandlerUtils.waitForIdleSerialExecutor;
 import static com.android.testutils.MiscAsserts.assertContainsAll;
 import static com.android.testutils.MiscAsserts.assertContainsExactly;
@@ -167,8 +176,20 @@
 import static com.android.testutils.MiscAsserts.assertRunsInAtMost;
 import static com.android.testutils.MiscAsserts.assertSameElements;
 import static com.android.testutils.MiscAsserts.assertThrows;
+import static com.android.testutils.RecorderCallback.CallbackEntry.AVAILABLE;
+import static com.android.testutils.RecorderCallback.CallbackEntry.BLOCKED_STATUS;
+import static com.android.testutils.RecorderCallback.CallbackEntry.BLOCKED_STATUS_INT;
+import static com.android.testutils.RecorderCallback.CallbackEntry.LINK_PROPERTIES_CHANGED;
+import static com.android.testutils.RecorderCallback.CallbackEntry.LOSING;
+import static com.android.testutils.RecorderCallback.CallbackEntry.LOST;
+import static com.android.testutils.RecorderCallback.CallbackEntry.NETWORK_CAPS_UPDATED;
+import static com.android.testutils.RecorderCallback.CallbackEntry.RESUMED;
+import static com.android.testutils.RecorderCallback.CallbackEntry.SUSPENDED;
+import static com.android.testutils.RecorderCallback.CallbackEntry.UNAVAILABLE;
 import static com.android.testutils.TestPermissionUtil.runAsShell;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -207,12 +228,16 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManager.UidFrozenStateChangedCallback;
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
 import android.app.usage.NetworkStatsManager;
+import android.compat.testing.PlatformCompatChangeRule;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentProvider;
@@ -237,7 +262,6 @@
 import android.net.ConnectivityManager.PacketKeepalive;
 import android.net.ConnectivityManager.PacketKeepaliveCallback;
 import android.net.ConnectivityManager.TooManyRequestsException;
-import android.net.ConnectivityResources;
 import android.net.ConnectivitySettingsManager;
 import android.net.ConnectivityThread;
 import android.net.DataStallReportParcelable;
@@ -297,6 +321,7 @@
 import android.net.Uri;
 import android.net.VpnManager;
 import android.net.VpnTransportInfo;
+import android.net.connectivity.ConnectivityCompatChanges;
 import android.net.metrics.IpConnectivityLog;
 import android.net.netd.aidl.NativeUidRangeConfig;
 import android.net.networkstack.NetworkStackClientBase;
@@ -352,19 +377,24 @@
 import com.android.internal.util.WakeupMessage;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.ArrayTrackRecord;
 import com.android.net.module.util.CollectionUtils;
 import com.android.net.module.util.LocationPermissionChecker;
 import com.android.net.module.util.NetworkMonitorUtils;
 import com.android.networkstack.apishim.ConstantsShim;
 import com.android.networkstack.apishim.NetworkAgentConfigShimImpl;
+import com.android.networkstack.apishim.common.BroadcastOptionsShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
 import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
 import com.android.server.ConnectivityService.NetworkRequestInfo;
+import com.android.server.ConnectivityServiceTest.ConnectivityServiceDependencies.DestroySocketsWrapper;
 import com.android.server.ConnectivityServiceTest.ConnectivityServiceDependencies.ReportedInterfaces;
+import com.android.server.connectivity.ApplicationSelfCertifiedNetworkCapabilities;
+import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker;
 import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
 import com.android.server.connectivity.ClatCoordinator;
 import com.android.server.connectivity.ConnectivityFlags;
+import com.android.server.connectivity.ConnectivityResources;
 import com.android.server.connectivity.MultinetworkPolicyTracker;
 import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies;
 import com.android.server.connectivity.Nat464Xlat;
@@ -388,6 +418,9 @@
 import com.android.testutils.TestableNetworkCallback;
 import com.android.testutils.TestableNetworkOfferCallback;
 
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -457,6 +490,9 @@
     @Rule
     public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
 
+    @Rule
+    public final PlatformCompatChangeRule compatChangeRule = new PlatformCompatChangeRule();
+
     private static final int TIMEOUT_MS = 2_000;
     // Broadcasts can take a long time to be delivered. The test will not wait for that long unless
     // there is a failure, so use a long timeout.
@@ -473,7 +509,7 @@
     // complete before callbacks are verified.
     private static final int TEST_REQUEST_TIMEOUT_MS = 150;
 
-    private static final int UNREASONABLY_LONG_ALARM_WAIT_MS = 1000;
+    private static final int UNREASONABLY_LONG_ALARM_WAIT_MS = 2_000;
 
     private static final long TIMESTAMP = 1234L;
 
@@ -504,6 +540,10 @@
     private static final int TEST_PACKAGE_UID = 123;
     private static final int TEST_PACKAGE_UID2 = 321;
     private static final int TEST_PACKAGE_UID3 = 456;
+
+    private static final int PACKET_WAKEUP_MASK = 0xffff0000;
+    private static final int PACKET_WAKEUP_MARK = 0x88880000;
+
     private static final String ALWAYS_ON_PACKAGE = "com.android.test.alwaysonvpn";
 
     private static final String INTERFACE_NAME = "interface";
@@ -523,11 +563,12 @@
     private MockContext mServiceContext;
     private HandlerThread mCsHandlerThread;
     private ConnectivityServiceDependencies mDeps;
+    private AutomaticOnOffKeepaliveTrackerDependencies mAutoOnOffKeepaliveDependencies;
     private ConnectivityService mService;
     private WrappedConnectivityManager mCm;
-    private TestNetworkAgentWrapper mWiFiNetworkAgent;
-    private TestNetworkAgentWrapper mCellNetworkAgent;
-    private TestNetworkAgentWrapper mEthernetNetworkAgent;
+    private TestNetworkAgentWrapper mWiFiAgent;
+    private TestNetworkAgentWrapper mCellAgent;
+    private TestNetworkAgentWrapper mEthernetAgent;
     private MockVpn mMockVpn;
     private Context mContext;
     private NetworkPolicyCallback mPolicyCallback;
@@ -573,6 +614,9 @@
     @Mock BpfNetMaps mBpfNetMaps;
     @Mock CarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
     @Mock TetheringManager mTetheringManager;
+    @Mock BroadcastOptionsShim mBroadcastOptionsShim;
+    @Mock ActivityManager mActivityManager;
+    @Mock DestroySocketsWrapper mDestroySocketsWrapper;
 
     // BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the
     // underlying binder calls.
@@ -696,6 +740,7 @@
             if (Context.BATTERY_STATS_SERVICE.equals(name)) return mBatteryStatsManager;
             if (Context.PAC_PROXY_SERVICE.equals(name)) return mPacProxyManager;
             if (Context.TETHERING_SERVICE.equals(name)) return mTetheringManager;
+            if (Context.ACTIVITY_SERVICE.equals(name)) return mActivityManager;
             return super.getSystemService(name);
         }
 
@@ -820,6 +865,27 @@
             // null should not pass the test
             return null;
         }
+
+        @Override
+        public void sendStickyBroadcast(Intent intent, Bundle options) {
+            // Verify that delivery group policy APIs were used on U.
+            if (mDeps.isAtLeastU() && CONNECTIVITY_ACTION.equals(intent.getAction())) {
+                final NetworkInfo ni = intent.getParcelableExtra(EXTRA_NETWORK_INFO,
+                        NetworkInfo.class);
+                try {
+                    verify(mBroadcastOptionsShim).setDeliveryGroupPolicy(
+                            eq(ConstantsShim.DELIVERY_GROUP_POLICY_MOST_RECENT));
+                    verify(mBroadcastOptionsShim).setDeliveryGroupMatchingKey(
+                            eq(CONNECTIVITY_ACTION),
+                            eq(createDeliveryGroupKeyForConnectivityAction(ni)));
+                    verify(mBroadcastOptionsShim).setDeferralPolicy(
+                            eq(ConstantsShim.DEFERRAL_POLICY_UNTIL_ACTIVE));
+                } catch (UnsupportedApiLevelException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            super.sendStickyBroadcast(intent, options);
+        }
     }
 
     // This was only added in the T SDK, but this test needs to build against the R+S SDKs, too.
@@ -850,9 +916,9 @@
 
     private void waitForIdle() {
         HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
-        waitForIdle(mCellNetworkAgent, TIMEOUT_MS);
-        waitForIdle(mWiFiNetworkAgent, TIMEOUT_MS);
-        waitForIdle(mEthernetNetworkAgent, TIMEOUT_MS);
+        waitForIdle(mCellAgent, TIMEOUT_MS);
+        waitForIdle(mWiFiAgent, TIMEOUT_MS);
+        waitForIdle(mEthernetAgent, TIMEOUT_MS);
         HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
         HandlerUtils.waitForIdle(ConnectivityThread.get(), TIMEOUT_MS);
     }
@@ -875,15 +941,15 @@
 
         // Bring up a network that we can use to send messages to ConnectivityService.
         ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);
         b.expectBroadcast();
-        Network n = mWiFiNetworkAgent.getNetwork();
+        Network n = mWiFiAgent.getNetwork();
         assertNotNull(n);
 
         // Tests that calling waitForIdle waits for messages to be processed.
         for (int i = 0; i < attempts; i++) {
-            mWiFiNetworkAgent.setSignalStrength(i);
+            mWiFiAgent.setSignalStrength(i);
             waitForIdle();
             assertEquals(i, mCm.getNetworkCapabilities(n).getSignalStrength());
         }
@@ -895,16 +961,16 @@
     public void verifyThatNotWaitingForIdleCausesRaceConditions() throws Exception {
         // Bring up a network that we can use to send messages to ConnectivityService.
         ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);
         b.expectBroadcast();
-        Network n = mWiFiNetworkAgent.getNetwork();
+        Network n = mWiFiAgent.getNetwork();
         assertNotNull(n);
 
         // Ensure that not calling waitForIdle causes a race condition.
         final int attempts = 50;  // Causes the test to take about 200ms on bullhead-eng.
         for (int i = 0; i < attempts; i++) {
-            mWiFiNetworkAgent.setSignalStrength(i);
+            mWiFiAgent.setSignalStrength(i);
             if (i != mCm.getNetworkCapabilities(n).getSignalStrength()) {
                 // We hit a race condition, as expected. Pass the test.
                 return;
@@ -1019,7 +1085,7 @@
         }
 
         private void onValidationRequested() throws Exception {
-            if (SdkLevel.isAtLeastT()) {
+            if (mDeps.isAtLeastT()) {
                 verify(mNetworkMonitor).notifyNetworkConnectedParcel(any());
             } else {
                 verify(mNetworkMonitor).notifyNetworkConnected(any(), any());
@@ -1058,15 +1124,30 @@
          * @param validated Indicate if network should pretend to be validated.
          */
         public void connect(boolean validated) {
-            connect(validated, true, false /* isStrictMode */);
+            connect(validated, true, false /* privateDnsProbeSent */);
         }
 
         /**
          * Transition this NetworkAgent to CONNECTED state.
+         *
          * @param validated Indicate if network should pretend to be validated.
+         *                  Note that if this is true, this method will mock the NetworkMonitor
+         *                  probes to pretend the network is invalid after it validated once,
+         *                  so that subsequent attempts (with mNetworkMonitor.forceReevaluation)
+         *                  will fail unless setNetworkValid is called again manually.
          * @param hasInternet Indicate if network should pretend to have NET_CAPABILITY_INTERNET.
+         * @param privateDnsProbeSent whether the private DNS probe should be considered to have
+         *                            been sent, assuming |validated| is true.
+         *                            If |validated| is false, |privateDnsProbeSent| is not used.
+         *                            If |validated| is true and |privateDnsProbeSent| is false,
+         *                            the probe has not been sent.
+         *                            If |validated| is true and |privateDnsProbeSent| is true,
+         *                            the probe has been sent and has succeeded. When the NM probes
+         *                            are mocked to be invalid, private DNS is the reason this
+         *                            network is invalid ; see @param |validated|.
          */
-        public void connect(boolean validated, boolean hasInternet, boolean isStrictMode) {
+        public void connect(boolean validated, boolean hasInternet,
+                boolean privateDnsProbeSent) {
             final ConditionVariable validatedCv = new ConditionVariable();
             final ConditionVariable capsChangedCv = new ConditionVariable();
             final NetworkRequest request = new NetworkRequest.Builder()
@@ -1074,7 +1155,7 @@
                     .clearCapabilities()
                     .build();
             if (validated) {
-                setNetworkValid(isStrictMode);
+                setNetworkValid(privateDnsProbeSent);
             }
             final NetworkCallback callback = new NetworkCallback() {
                 public void onCapabilitiesChanged(Network network,
@@ -1099,14 +1180,15 @@
             if (validated) {
                 // Wait for network to validate.
                 waitFor(validatedCv);
-                setNetworkInvalid(isStrictMode);
+                setNetworkInvalid(privateDnsProbeSent);
             }
             mCm.unregisterNetworkCallback(callback);
         }
 
-        public void connectWithCaptivePortal(String redirectUrl, boolean isStrictMode) {
-            setNetworkPortal(redirectUrl, isStrictMode);
-            connect(false, true /* hasInternet */, isStrictMode);
+        public void connectWithCaptivePortal(String redirectUrl,
+                boolean privateDnsProbeSent) {
+            setNetworkPortal(redirectUrl, privateDnsProbeSent);
+            connect(false, true /* hasInternet */, privateDnsProbeSent);
         }
 
         public void connectWithPartialConnectivity() {
@@ -1114,16 +1196,16 @@
             connect(false);
         }
 
-        public void connectWithPartialValidConnectivity(boolean isStrictMode) {
-            setNetworkPartialValid(isStrictMode);
-            connect(false, true /* hasInternet */, isStrictMode);
+        public void connectWithPartialValidConnectivity(boolean privateDnsProbeSent) {
+            setNetworkPartialValid(privateDnsProbeSent);
+            connect(false, true /* hasInternet */, privateDnsProbeSent);
         }
 
-        void setNetworkValid(boolean isStrictMode) {
+        void setNetworkValid(boolean privateDnsProbeSent) {
             mNmValidationResult = NETWORK_VALIDATION_RESULT_VALID;
             mNmValidationRedirectUrl = null;
             int probesSucceeded = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS;
-            if (isStrictMode) {
+            if (privateDnsProbeSent) {
                 probesSucceeded |= NETWORK_VALIDATION_PROBE_PRIVDNS;
             }
             // The probesCompleted equals to probesSucceeded for the case of valid network, so put
@@ -1131,15 +1213,16 @@
             setProbesStatus(probesSucceeded, probesSucceeded);
         }
 
-        void setNetworkInvalid(boolean isStrictMode) {
+        void setNetworkInvalid(boolean invalidBecauseOfPrivateDns) {
             mNmValidationResult = VALIDATION_RESULT_INVALID;
             mNmValidationRedirectUrl = null;
             int probesCompleted = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS
                     | NETWORK_VALIDATION_PROBE_HTTP;
             int probesSucceeded = 0;
-            // If the isStrictMode is true, it means the network is invalid when NetworkMonitor
-            // tried to validate the private DNS but failed.
-            if (isStrictMode) {
+            // If |invalidBecauseOfPrivateDns| is true, it means the network is invalid because
+            // NetworkMonitor tried to validate the private DNS but failed. Therefore it
+            // didn't get a chance to try the HTTP probe.
+            if (invalidBecauseOfPrivateDns) {
                 probesCompleted &= ~NETWORK_VALIDATION_PROBE_HTTP;
                 probesSucceeded = probesCompleted;
                 probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS;
@@ -1147,14 +1230,14 @@
             setProbesStatus(probesCompleted, probesSucceeded);
         }
 
-        void setNetworkPortal(String redirectUrl, boolean isStrictMode) {
-            setNetworkInvalid(isStrictMode);
+        void setNetworkPortal(String redirectUrl, boolean privateDnsProbeSent) {
+            setNetworkInvalid(privateDnsProbeSent);
             mNmValidationRedirectUrl = redirectUrl;
             // Suppose the portal is found when NetworkMonitor probes NETWORK_VALIDATION_PROBE_HTTP
             // in the beginning, so the NETWORK_VALIDATION_PROBE_HTTPS hasn't probed yet.
             int probesCompleted = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP;
             int probesSucceeded = VALIDATION_RESULT_INVALID;
-            if (isStrictMode) {
+            if (privateDnsProbeSent) {
                 probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS;
             }
             setProbesStatus(probesCompleted, probesSucceeded);
@@ -1169,7 +1252,7 @@
             setProbesStatus(probesCompleted, probesSucceeded);
         }
 
-        void setNetworkPartialValid(boolean isStrictMode) {
+        void setNetworkPartialValid(boolean privateDnsProbeSent) {
             setNetworkPartial();
             mNmValidationResult |= NETWORK_VALIDATION_RESULT_VALID;
             mNmValidationRedirectUrl = null;
@@ -1178,7 +1261,7 @@
             int probesSucceeded = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP;
             // Assume the partial network cannot pass the private DNS validation as well, so only
             // add NETWORK_VALIDATION_PROBE_DNS in probesCompleted but not probesSucceeded.
-            if (isStrictMode) {
+            if (privateDnsProbeSent) {
                 probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS;
             }
             setProbesStatus(probesCompleted, probesSucceeded);
@@ -1473,8 +1556,9 @@
             registerAgent(false /* isAlwaysMetered */, uids, makeLinkProperties());
         }
 
-        private void connect(boolean validated, boolean hasInternet, boolean isStrictMode) {
-            mMockNetworkAgent.connect(validated, hasInternet, isStrictMode);
+        private void connect(boolean validated, boolean hasInternet,
+                boolean privateDnsProbeSent) {
+            mMockNetworkAgent.connect(validated, hasInternet, privateDnsProbeSent);
         }
 
         private void connect(boolean validated) {
@@ -1491,10 +1575,10 @@
         }
 
         public void establish(LinkProperties lp, int uid, Set<UidRange> ranges, boolean validated,
-                boolean hasInternet, boolean isStrictMode) throws Exception {
+                boolean hasInternet, boolean privateDnsProbeSent) throws Exception {
             setOwnerAndAdminUid(uid);
             registerAgent(false, ranges, lp);
-            connect(validated, hasInternet, isStrictMode);
+            connect(validated, hasInternet, privateDnsProbeSent);
             waitForIdle();
         }
 
@@ -1507,11 +1591,11 @@
             establish(lp, uid, uidRangesForUids(uid), true, true, false);
         }
 
-        public void establishForMyUid(boolean validated, boolean hasInternet, boolean isStrictMode)
-                throws Exception {
+        public void establishForMyUid(boolean validated, boolean hasInternet,
+                boolean privateDnsProbeSent) throws Exception {
             final int uid = Process.myUid();
             establish(makeLinkProperties(), uid, uidRangesForUids(uid), validated, hasInternet,
-                    isStrictMode);
+                    privateDnsProbeSent);
         }
 
         public void establishForMyUid() throws Exception {
@@ -1784,7 +1868,8 @@
         doReturn(mResources).when(mockResContext).getResources();
         ConnectivityResources.setResourcesContextForTest(mockResContext);
         mDeps = new ConnectivityServiceDependencies(mockResContext);
-
+        mAutoOnOffKeepaliveDependencies =
+                new AutomaticOnOffKeepaliveTrackerDependencies(mServiceContext);
         mService = new ConnectivityService(mServiceContext,
                 mMockDnsResolver,
                 mock(IpConnectivityLog.class),
@@ -1839,10 +1924,15 @@
         doReturn(0).when(mResources).getInteger(R.integer.config_activelyPreferBadWifi);
         doReturn(true).when(mResources)
                 .getBoolean(R.bool.config_cellular_radio_timesharing_capable);
+        doReturn(PACKET_WAKEUP_MASK).when(mResources).getInteger(
+                R.integer.config_networkWakeupPacketMask);
+        doReturn(PACKET_WAKEUP_MARK).when(mResources).getInteger(
+                R.integer.config_networkWakeupPacketMark);
     }
 
     class ConnectivityServiceDependencies extends ConnectivityService.Dependencies {
         final ConnectivityResources mConnRes;
+        final ArraySet<Pair<Long, Integer>> mEnabledChangeIds = new ArraySet<>();
 
         ConnectivityServiceDependencies(final Context mockResContext) {
             mConnRes = new ConnectivityResources(mockResContext);
@@ -1885,6 +1975,12 @@
         }
 
         @Override
+        public AutomaticOnOffKeepaliveTracker makeAutomaticOnOffKeepaliveTracker(final Context c,
+                final Handler h) {
+            return new AutomaticOnOffKeepaliveTracker(c, h, mAutoOnOffKeepaliveDependencies);
+        }
+
+        @Override
         public ConnectivityResources getResources(final Context ctx) {
             return mConnRes;
         }
@@ -1902,7 +1998,7 @@
         @Override
         public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
                 @NonNull final Context context, @NonNull final TelephonyManager tm) {
-            return SdkLevel.isAtLeastT() ? mCarrierPrivilegeAuthenticator : null;
+            return mDeps.isAtLeastT() ? mCarrierPrivilegeAuthenticator : null;
         }
 
         @Override
@@ -1990,15 +2086,72 @@
         }
 
         @Override
-        public boolean isFeatureEnabled(Context context, String name, boolean defaultEnabled) {
+        public boolean isFeatureEnabled(Context context, String name) {
             switch (name) {
                 case ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER:
                     return true;
+                case KEY_DESTROY_FROZEN_SOCKETS_VERSION:
+                    return true;
                 default:
-                    return super.isFeatureEnabled(context, name, defaultEnabled);
+                    return super.isFeatureEnabled(context, name);
             }
         }
 
+        public void setChangeIdEnabled(final boolean enabled, final long changeId, final int uid) {
+            final Pair<Long, Integer> data = new Pair<>(changeId, uid);
+            // mEnabledChangeIds is read on the handler thread and maybe the test thread, so
+            // make sure both threads see it before continuing.
+            visibleOnHandlerThread(mCsHandlerThread.getThreadHandler(), () -> {
+                if (enabled) {
+                    mEnabledChangeIds.add(data);
+                } else {
+                    mEnabledChangeIds.remove(data);
+                }
+            });
+        }
+
+        @Override
+        public boolean isChangeEnabled(final long changeId, final int uid) {
+            return mEnabledChangeIds.contains(new Pair<>(changeId, uid));
+        }
+
+        // In AOSP, build version codes are all over the place (e.g. at the time of this writing
+        // U == V). Define custom ones.
+        private static final int VERSION_UNMOCKED = -1;
+        private static final int VERSION_R = 1;
+        private static final int VERSION_S = 2;
+        private static final int VERSION_T = 3;
+        private static final int VERSION_U = 4;
+        private static final int VERSION_V = 5;
+        private static final int VERSION_MAX = VERSION_V;
+        private int mSdkLevel = VERSION_UNMOCKED;
+
+        private void setBuildSdk(final int sdkLevel) {
+            if (sdkLevel > VERSION_MAX) {
+                throw new IllegalArgumentException("setBuildSdk must not be called with"
+                        + " Build.VERSION constants but Dependencies.VERSION_* constants");
+            }
+            visibleOnHandlerThread(mCsHandlerThread.getThreadHandler(), () -> mSdkLevel = sdkLevel);
+        }
+
+        @Override
+        public boolean isAtLeastS() {
+            return mSdkLevel == VERSION_UNMOCKED ? super.isAtLeastS()
+                    : mSdkLevel >= VERSION_S;
+        }
+
+        @Override
+        public boolean isAtLeastT() {
+            return mSdkLevel == VERSION_UNMOCKED ? super.isAtLeastT()
+                    : mSdkLevel >= VERSION_T;
+        }
+
+        @Override
+        public boolean isAtLeastU() {
+            return mSdkLevel == VERSION_UNMOCKED ? super.isAtLeastU()
+                    : mSdkLevel >= VERSION_U;
+        }
+
         @Override
         public BpfNetMaps getBpfNetMaps(Context context, INetd netd) {
             return mBpfNetMaps;
@@ -2034,6 +2187,86 @@
             assertNotEquals(-1L, (long) mActiveRateLimit.getOrDefault(iface, -1L));
             mActiveRateLimit.put(iface, -1L);
         }
+
+        @Override
+        public BroadcastOptionsShim makeBroadcastOptionsShim(BroadcastOptions options) {
+            reset(mBroadcastOptionsShim);
+            return mBroadcastOptionsShim;
+        }
+
+        @GuardedBy("this")
+        private boolean mForceDisableCompatChangeCheck = true;
+
+        /**
+         * By default, the {@link #isChangeEnabled(long, String, UserHandle)} will always return
+         * true as the mForceDisableCompatChangeCheck is true and compat change check logic is
+         * never executed. The compat change check logic can be turned on by calling this method.
+         * If this method is called, the
+         * {@link libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges} or
+         * {@link libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges} must be
+         * used to turn on/off the compat change flag.
+         */
+        private void enableCompatChangeCheck() {
+            synchronized (this) {
+                mForceDisableCompatChangeCheck = false;
+            }
+        }
+
+        @Override
+        public boolean isChangeEnabled(long changeId,
+                @NonNull final String packageName,
+                @NonNull final UserHandle user) {
+            synchronized (this) {
+                if (mForceDisableCompatChangeCheck) {
+                    return false;
+                } else {
+                    return super.isChangeEnabled(changeId, packageName, user);
+                }
+            }
+        }
+
+        // Class to be mocked and used to verify destroy sockets methods call
+        public class DestroySocketsWrapper {
+            public void destroyLiveTcpSockets(final Set<Range<Integer>> ranges,
+                    final Set<Integer> exemptUids){}
+            public void destroyLiveTcpSocketsByOwnerUids(final Set<Integer> ownerUids){}
+        }
+
+        @Override @SuppressWarnings("DirectInvocationOnMock")
+        public void destroyLiveTcpSockets(final Set<Range<Integer>> ranges,
+                final Set<Integer> exemptUids) {
+            // Call mocked destroyLiveTcpSockets so that test can verify this method call
+            mDestroySocketsWrapper.destroyLiveTcpSockets(ranges, exemptUids);
+        }
+
+        @Override @SuppressWarnings("DirectInvocationOnMock")
+        public void destroyLiveTcpSocketsByOwnerUids(final Set<Integer> ownerUids) {
+            // Call mocked destroyLiveTcpSocketsByOwnerUids so that test can verify this method call
+            mDestroySocketsWrapper.destroyLiveTcpSocketsByOwnerUids(ownerUids);
+        }
+
+        final ArrayTrackRecord<Long>.ReadHead mScheduledEvaluationTimeouts =
+                new ArrayTrackRecord<Long>().newReadHead();
+        @Override
+        public void scheduleEvaluationTimeout(@NonNull Handler handler,
+                @NonNull final Network network, final long delayMs) {
+            mScheduledEvaluationTimeouts.add(delayMs);
+            super.scheduleEvaluationTimeout(handler, network, delayMs);
+        }
+    }
+
+    private class AutomaticOnOffKeepaliveTrackerDependencies
+            extends AutomaticOnOffKeepaliveTracker.Dependencies {
+        AutomaticOnOffKeepaliveTrackerDependencies(Context context) {
+            super(context);
+        }
+
+        @Override
+        public boolean isFeatureEnabled(@NonNull final String name, final boolean defaultEnabled) {
+            // Tests for enabling the feature are verified in AutomaticOnOffKeepaliveTrackerTest.
+            // Assuming enabled here to focus on ConnectivityService tests.
+            return true;
+        }
     }
 
     private static void initAlarmManager(final AlarmManager am, final Handler alarmHandler) {
@@ -2067,17 +2300,17 @@
         unregisterDefaultNetworkCallbacks();
         maybeTearDownEnterpriseNetwork();
         setAlwaysOnNetworks(false);
-        if (mCellNetworkAgent != null) {
-            mCellNetworkAgent.disconnect();
-            mCellNetworkAgent = null;
+        if (mCellAgent != null) {
+            mCellAgent.disconnect();
+            mCellAgent = null;
         }
-        if (mWiFiNetworkAgent != null) {
-            mWiFiNetworkAgent.disconnect();
-            mWiFiNetworkAgent = null;
+        if (mWiFiAgent != null) {
+            mWiFiAgent.disconnect();
+            mWiFiAgent = null;
         }
-        if (mEthernetNetworkAgent != null) {
-            mEthernetNetworkAgent.disconnect();
-            mEthernetNetworkAgent = null;
+        if (mEthernetAgent != null) {
+            mEthernetAgent.disconnect();
+            mEthernetAgent = null;
         }
 
         if (mQosCallbackMockHelper != null) {
@@ -2091,7 +2324,9 @@
         ConnectivityResources.setResourcesContextForTest(null);
 
         mCsHandlerThread.quitSafely();
+        mCsHandlerThread.join();
         mAlarmManagerThread.quitSafely();
+        mAlarmManagerThread.join();
     }
 
     private void mockDefaultPackages() throws Exception {
@@ -2140,13 +2375,13 @@
         }
         switch (transport) {
             case TRANSPORT_WIFI:
-                assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+                assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
                 break;
             case TRANSPORT_CELLULAR:
-                assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+                assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
                 break;
             case TRANSPORT_ETHERNET:
-                assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+                assertEquals(mEthernetAgent.getNetwork(), mCm.getActiveNetwork());
                 break;
             default:
                 break;
@@ -2214,22 +2449,15 @@
         }
     }
 
-    /** Expects that {@code count} CONNECTIVITY_ACTION broadcasts are received. */
-    private ExpectedBroadcast registerConnectivityBroadcast(final int count) {
-        return registerConnectivityBroadcastThat(count, intent -> true);
-    }
-
-    private ExpectedBroadcast registerConnectivityBroadcastThat(final int count,
+    private ExpectedBroadcast registerBroadcastReceiverThat(final String action, final int count,
             @NonNull final Predicate<Intent> filter) {
-        final IntentFilter intentFilter = new IntentFilter(CONNECTIVITY_ACTION);
+        final IntentFilter intentFilter = new IntentFilter(action);
         // AtomicReference allows receiver to access expected even though it is constructed later.
         final AtomicReference<ExpectedBroadcast> expectedRef = new AtomicReference<>();
         final BroadcastReceiver receiver = new BroadcastReceiver() {
             private int mRemaining = count;
             public void onReceive(Context context, Intent intent) {
-                final int type = intent.getIntExtra(EXTRA_NETWORK_TYPE, -1);
-                final NetworkInfo ni = intent.getParcelableExtra(EXTRA_NETWORK_INFO);
-                Log.d(TAG, "Received CONNECTIVITY_ACTION type=" + type + " ni=" + ni);
+                logIntent(intent);
                 if (!filter.test(intent)) return;
                 if (--mRemaining == 0) {
                     expectedRef.get().complete(intent);
@@ -2242,39 +2470,49 @@
         return expected;
     }
 
+    private void logIntent(Intent intent) {
+        final String action = intent.getAction();
+        if (CONNECTIVITY_ACTION.equals(action)) {
+            final int type = intent.getIntExtra(EXTRA_NETWORK_TYPE, -1);
+            final NetworkInfo ni = intent.getParcelableExtra(EXTRA_NETWORK_INFO);
+            Log.d(TAG, "Received " + action + ", type=" + type + " ni=" + ni);
+        } else if (PROXY_CHANGE_ACTION.equals(action)) {
+            final ProxyInfo proxy = (ProxyInfo) intent.getExtra(
+                    Proxy.EXTRA_PROXY_INFO, ProxyInfo.buildPacProxy(Uri.EMPTY));
+            Log.d(TAG, "Received " + action + ", proxy = " + proxy);
+        } else {
+            throw new IllegalArgumentException("Unsupported logging " + action);
+        }
+    }
+
+    /** Expects that {@code count} CONNECTIVITY_ACTION broadcasts are received. */
+    private ExpectedBroadcast expectConnectivityAction(final int count) {
+        return registerBroadcastReceiverThat(CONNECTIVITY_ACTION, count, intent -> true);
+    }
+
+    private ExpectedBroadcast expectConnectivityAction(int type, NetworkInfo.DetailedState state) {
+        return registerBroadcastReceiverThat(CONNECTIVITY_ACTION, 1, intent -> {
+            final int actualType = intent.getIntExtra(EXTRA_NETWORK_TYPE, -1);
+            final NetworkInfo ni = intent.getParcelableExtra(EXTRA_NETWORK_INFO);
+            return type == actualType
+                    && state == ni.getDetailedState()
+                    && extraInfoInBroadcastHasExpectedNullness(ni);
+        });
+    }
+
+    /** Expects that PROXY_CHANGE_ACTION broadcast is received. */
+    private ExpectedBroadcast expectProxyChangeAction() {
+        return registerBroadcastReceiverThat(PROXY_CHANGE_ACTION, 1, intent -> true);
+    }
+
     private ExpectedBroadcast expectProxyChangeAction(ProxyInfo proxy) {
-        return registerPacProxyBroadcastThat(intent -> {
+        return registerBroadcastReceiverThat(PROXY_CHANGE_ACTION, 1, intent -> {
             final ProxyInfo actualProxy = (ProxyInfo) intent.getExtra(Proxy.EXTRA_PROXY_INFO,
                     ProxyInfo.buildPacProxy(Uri.EMPTY));
             return proxy.equals(actualProxy);
         });
     }
 
-    private ExpectedBroadcast registerPacProxyBroadcast() {
-        return registerPacProxyBroadcastThat(intent -> true);
-    }
-
-    private ExpectedBroadcast registerPacProxyBroadcastThat(
-            @NonNull final Predicate<Intent> filter) {
-        final IntentFilter intentFilter = new IntentFilter(Proxy.PROXY_CHANGE_ACTION);
-        // AtomicReference allows receiver to access expected even though it is constructed later.
-        final AtomicReference<ExpectedBroadcast> expectedRef = new AtomicReference<>();
-        final BroadcastReceiver receiver = new BroadcastReceiver() {
-            public void onReceive(Context context, Intent intent) {
-                final ProxyInfo proxy = (ProxyInfo) intent.getExtra(
-                            Proxy.EXTRA_PROXY_INFO, ProxyInfo.buildPacProxy(Uri.EMPTY));
-                Log.d(TAG, "Receive PROXY_CHANGE_ACTION, proxy = " + proxy);
-                if (filter.test(intent)) {
-                    expectedRef.get().complete(intent);
-                }
-            }
-        };
-        final ExpectedBroadcast expected = new ExpectedBroadcast(receiver);
-        expectedRef.set(expected);
-        mServiceContext.registerReceiver(receiver, intentFilter);
-        return expected;
-    }
-
     private boolean extraInfoInBroadcastHasExpectedNullness(NetworkInfo ni) {
         final DetailedState state = ni.getDetailedState();
         if (state == DetailedState.CONNECTED && ni.getExtraInfo() == null) return false;
@@ -2290,16 +2528,6 @@
         return true;
     }
 
-    private ExpectedBroadcast expectConnectivityAction(int type, NetworkInfo.DetailedState state) {
-        return registerConnectivityBroadcastThat(1, intent -> {
-            final int actualType = intent.getIntExtra(EXTRA_NETWORK_TYPE, -1);
-            final NetworkInfo ni = intent.getParcelableExtra(EXTRA_NETWORK_INFO);
-            return type == actualType
-                    && state == ni.getDetailedState()
-                    && extraInfoInBroadcastHasExpectedNullness(ni);
-        });
-    }
-
     @Test
     public void testNetworkTypes() {
         // Ensure that our mocks for the networkAttributes config variable work as expected. If they
@@ -2320,10 +2548,10 @@
     @Test
     public void testNetworkFeature() throws Exception {
         // Connect the cell agent and wait for the connected broadcast.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.addCapability(NET_CAPABILITY_SUPL);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.addCapability(NET_CAPABILITY_SUPL);
         ExpectedBroadcast b = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED);
-        mCellNetworkAgent.connect(true);
+        mCellAgent.connect(true);
         b.expectBroadcast();
 
         // Build legacy request for SUPL.
@@ -2334,56 +2562,56 @@
                 ConnectivityManager.REQUEST_ID_UNSET, NetworkRequest.Type.REQUEST);
 
         // File request, withdraw it and make sure no broadcast is sent
-        b = registerConnectivityBroadcast(1);
+        b = expectConnectivityAction(1);
         final TestNetworkCallback callback = new TestNetworkCallback();
         mCm.requestNetwork(legacyRequest, callback);
-        callback.expect(CallbackEntry.AVAILABLE, mCellNetworkAgent);
+        callback.expect(AVAILABLE, mCellAgent);
         mCm.unregisterNetworkCallback(callback);
         b.expectNoBroadcast(800);  // 800ms long enough to at least flake if this is sent
 
         // Disconnect the network and expect mobile disconnected broadcast.
         b = expectConnectivityAction(TYPE_MOBILE, DetailedState.DISCONNECTED);
-        mCellNetworkAgent.disconnect();
+        mCellAgent.disconnect();
         b.expectBroadcast();
     }
 
     @Test
     public void testLingering() throws Exception {
         verifyNoNetwork();
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         assertNull(mCm.getActiveNetworkInfo());
         assertNull(mCm.getActiveNetwork());
         // Test bringing up validated cellular.
         ExpectedBroadcast b = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED);
-        mCellNetworkAgent.connect(true);
+        mCellAgent.connect(true);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         assertLength(2, mCm.getAllNetworks());
-        assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) ||
-                mCm.getAllNetworks()[1].equals(mCm.getActiveNetwork()));
-        assertTrue(mCm.getAllNetworks()[0].equals(mWiFiNetworkAgent.getNetwork()) ||
-                mCm.getAllNetworks()[1].equals(mWiFiNetworkAgent.getNetwork()));
+        assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork())
+                || mCm.getAllNetworks()[1].equals(mCm.getActiveNetwork()));
+        assertTrue(mCm.getAllNetworks()[0].equals(mWiFiAgent.getNetwork())
+                || mCm.getAllNetworks()[1].equals(mWiFiAgent.getNetwork()));
         // Test bringing up validated WiFi.
-        b = registerConnectivityBroadcast(2);
-        mWiFiNetworkAgent.connect(true);
+        b = expectConnectivityAction(2);
+        mWiFiAgent.connect(true);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_WIFI);
         assertLength(2, mCm.getAllNetworks());
-        assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) ||
-                mCm.getAllNetworks()[1].equals(mCm.getActiveNetwork()));
-        assertTrue(mCm.getAllNetworks()[0].equals(mCellNetworkAgent.getNetwork()) ||
-                mCm.getAllNetworks()[1].equals(mCellNetworkAgent.getNetwork()));
+        assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork())
+                || mCm.getAllNetworks()[1].equals(mCm.getActiveNetwork()));
+        assertTrue(mCm.getAllNetworks()[0].equals(mCellAgent.getNetwork())
+                || mCm.getAllNetworks()[1].equals(mCellAgent.getNetwork()));
         // Test cellular linger timeout.
-        mCellNetworkAgent.expectDisconnected();
+        mCellAgent.expectDisconnected();
         waitForIdle();
         assertLength(1, mCm.getAllNetworks());
         verifyActiveNetwork(TRANSPORT_WIFI);
         assertLength(1, mCm.getAllNetworks());
         assertEquals(mCm.getAllNetworks()[0], mCm.getActiveNetwork());
         // Test WiFi disconnect.
-        b = registerConnectivityBroadcast(1);
-        mWiFiNetworkAgent.disconnect();
+        b = expectConnectivityAction(1);
+        mWiFiAgent.disconnect();
         b.expectBroadcast();
         verifyNoNetwork();
     }
@@ -2400,43 +2628,43 @@
 
         // 1. Create a network that is not requested by anyone, and does not satisfy any of the
         // default requests. Verify that the network will be inactive instead of torn down.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connectWithoutInternet();
-        listenCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connectWithoutInternet();
+        listenCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         listenCallback.assertNoCallback();
 
         // Verify that the network will be torn down after nascent expiry. A small period of time
         // is added in case of flakiness.
         final int nascentTimeoutMs =
                 mService.mNascentDelayMs + mService.mNascentDelayMs / 4;
-        listenCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent, nascentTimeoutMs);
+        listenCallback.expect(LOST, mWiFiAgent, nascentTimeoutMs);
 
         // 2. Create a network that is satisfied by a request comes later.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connectWithoutInternet();
-        listenCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connectWithoutInternet();
+        listenCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         final NetworkRequest wifiRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_WIFI).build();
         final TestNetworkCallback wifiCallback = new TestNetworkCallback();
         mCm.requestNetwork(wifiRequest, wifiCallback);
-        wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
 
         // Verify that the network will be kept since the request is still satisfied. And is able
         // to get disconnected as usual if the request is released after the nascent timer expires.
         listenCallback.assertNoCallback(nascentTimeoutMs);
         mCm.unregisterNetworkCallback(wifiCallback);
-        listenCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        listenCallback.expect(LOST, mWiFiAgent);
 
         // 3. Create a network that is satisfied by a request comes later.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connectWithoutInternet();
-        listenCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connectWithoutInternet();
+        listenCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         mCm.requestNetwork(wifiRequest, wifiCallback);
-        wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
 
         // Verify that the network will still be torn down after the request gets removed.
         mCm.unregisterNetworkCallback(wifiCallback);
-        listenCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        listenCallback.expect(LOST, mWiFiAgent);
 
         // There is no need to ensure that LOSING is never sent in the common case that the
         // network immediately satisfies a request that was already present, because it is already
@@ -2469,20 +2697,20 @@
                 .addCapability(NET_CAPABILITY_FOREGROUND).build(), fgMobileListenCallback);
 
         // Connect wifi, which satisfies default request.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
-        wifiListenCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
+        wifiListenCallback.expectAvailableThenValidatedCallbacks(mWiFiAgent);
 
         // Connect a cellular network, verify that satisfies only the background callback.
         setAlwaysOnNetworks(true);
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        bgMobileListenCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        bgMobileListenCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
         fgMobileListenCallback.assertNoCallback();
-        assertFalse(isForegroundNetwork(mCellNetworkAgent));
+        assertFalse(isForegroundNetwork(mCellAgent));
 
-        mCellNetworkAgent.disconnect();
-        bgMobileListenCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        mCellAgent.disconnect();
+        bgMobileListenCallback.expect(LOST, mCellAgent);
         fgMobileListenCallback.assertNoCallback();
 
         mCm.unregisterNetworkCallback(wifiListenCallback);
@@ -2547,34 +2775,34 @@
             final boolean cellRadioTimesharingCapable) throws Exception {
         mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
         // Test bringing up unvalidated WiFi
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        ExpectedBroadcast b = registerConnectivityBroadcast(1);
-        mWiFiNetworkAgent.connect(false);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        ExpectedBroadcast b = expectConnectivityAction(1);
+        mWiFiAgent.connect(false);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test bringing up unvalidated cellular
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(false);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(false);
         waitForIdle();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test cellular disconnect.
-        mCellNetworkAgent.disconnect();
+        mCellAgent.disconnect();
         waitForIdle();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test bringing up validated cellular
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        b = registerConnectivityBroadcast(2);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        b = expectConnectivityAction(2);
+        mCellAgent.connect(true);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         // Test cellular disconnect.
-        b = registerConnectivityBroadcast(2);
-        mCellNetworkAgent.disconnect();
+        b = expectConnectivityAction(2);
+        mCellAgent.disconnect();
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test WiFi disconnect.
-        b = registerConnectivityBroadcast(1);
-        mWiFiNetworkAgent.disconnect();
+        b = expectConnectivityAction(1);
+        mWiFiAgent.disconnect();
         b.expectBroadcast();
         verifyNoNetwork();
     }
@@ -2595,25 +2823,25 @@
             final boolean cellRadioTimesharingCapable) throws Exception {
         mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
         // Test bringing up unvalidated cellular.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        ExpectedBroadcast b = registerConnectivityBroadcast(1);
-        mCellNetworkAgent.connect(false);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        ExpectedBroadcast b = expectConnectivityAction(1);
+        mCellAgent.connect(false);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         // Test bringing up unvalidated WiFi.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        b = registerConnectivityBroadcast(2);
-        mWiFiNetworkAgent.connect(false);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        b = expectConnectivityAction(2);
+        mWiFiAgent.connect(false);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test WiFi disconnect.
-        b = registerConnectivityBroadcast(2);
-        mWiFiNetworkAgent.disconnect();
+        b = expectConnectivityAction(2);
+        mWiFiAgent.disconnect();
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         // Test cellular disconnect.
-        b = registerConnectivityBroadcast(1);
-        mCellNetworkAgent.disconnect();
+        b = expectConnectivityAction(1);
+        mCellAgent.disconnect();
         b.expectBroadcast();
         verifyNoNetwork();
     }
@@ -2634,28 +2862,28 @@
             final boolean cellRadioTimesharingCapable) throws Exception {
         mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
         // Test bringing up unvalidated WiFi.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        ExpectedBroadcast b = registerConnectivityBroadcast(1);
-        mWiFiNetworkAgent.connect(false);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        ExpectedBroadcast b = expectConnectivityAction(1);
+        mWiFiAgent.connect(false);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_WIFI);
-        assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
+        assertFalse(mCm.getNetworkCapabilities(mWiFiAgent.getNetwork()).hasCapability(
                 NET_CAPABILITY_VALIDATED));
         // Test bringing up validated cellular.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        b = registerConnectivityBroadcast(2);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        b = expectConnectivityAction(2);
+        mCellAgent.connect(true);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_CELLULAR);
-        assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
+        assertFalse(mCm.getNetworkCapabilities(mWiFiAgent.getNetwork()).hasCapability(
                 NET_CAPABILITY_VALIDATED));
         // Test cellular disconnect.
-        b = registerConnectivityBroadcast(2);
-        mCellNetworkAgent.disconnect();
+        b = expectConnectivityAction(2);
+        mCellAgent.disconnect();
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Unlingering a network should not cause it to be marked as validated.
-        assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
+        assertFalse(mCm.getNetworkCapabilities(mWiFiAgent.getNetwork()).hasCapability(
                 NET_CAPABILITY_VALIDATED));
     }
 
@@ -2712,7 +2940,7 @@
         if (expectLingering) {
             generalCb.expectLosing(net1);
         }
-        generalCb.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, net2);
+        generalCb.expectCaps(net2, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
         defaultCb.expectAvailableDoubleValidatedCallbacks(net2);
 
         // Make sure cell 1 is unwanted immediately if the radio can't time share, but only
@@ -2727,7 +2955,7 @@
             net1.expectDisconnected(TEST_CALLBACK_TIMEOUT_MS);
         }
         net1.disconnect();
-        generalCb.expect(CallbackEntry.LOST, net1);
+        generalCb.expect(LOST, net1);
 
         // Remove primary from net 2
         net2.setScore(new NetworkScore.Builder().build());
@@ -2757,13 +2985,14 @@
             // for any other request.
             generalCb.expectLosing(net2);
             net2.assertNotDisconnected(TEST_CALLBACK_TIMEOUT_MS);
-            generalCb.assertNoCallback();
+            // Timeout 0 because after a while LOST will actually arrive
+            generalCb.assertNoCallback(0 /* timeoutMs */);
             net2.expectDisconnected(UNREASONABLY_LONG_ALARM_WAIT_MS);
         } else {
             net2.expectDisconnected(TEST_CALLBACK_TIMEOUT_MS);
         }
         net2.disconnect();
-        generalCb.expect(CallbackEntry.LOST, net2);
+        generalCb.expect(LOST, net2);
         defaultCb.assertNoCallback();
 
         net3.disconnect();
@@ -2788,25 +3017,25 @@
             final boolean cellRadioTimesharingCapable) throws Exception {
         mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
         // Test bringing up validated cellular.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        ExpectedBroadcast b = registerConnectivityBroadcast(1);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        ExpectedBroadcast b = expectConnectivityAction(1);
+        mCellAgent.connect(true);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         // Test bringing up validated WiFi.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        b = registerConnectivityBroadcast(2);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        b = expectConnectivityAction(2);
+        mWiFiAgent.connect(true);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test WiFi getting really weak.
-        b = registerConnectivityBroadcast(2);
-        mWiFiNetworkAgent.adjustScore(-11);
+        b = expectConnectivityAction(2);
+        mWiFiAgent.adjustScore(-11);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         // Test WiFi restoring signal strength.
-        b = registerConnectivityBroadcast(2);
-        mWiFiNetworkAgent.adjustScore(11);
+        b = expectConnectivityAction(2);
+        mWiFiAgent.adjustScore(11);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_WIFI);
     }
@@ -2828,29 +3057,29 @@
         mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
         // Test bringing up WiFi without NET_CAPABILITY_INTERNET.
         // Expect it to be torn down immediately because it satisfies no requests.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connectWithoutInternet();
-        mWiFiNetworkAgent.expectDisconnected();
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connectWithoutInternet();
+        mWiFiAgent.expectDisconnected();
         // Test bringing up cellular without NET_CAPABILITY_INTERNET.
         // Expect it to be torn down immediately because it satisfies no requests.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mCellNetworkAgent.connectWithoutInternet();
-        mCellNetworkAgent.expectDisconnected();
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mCellAgent.connectWithoutInternet();
+        mCellAgent.expectDisconnected();
         // Test bringing up validated WiFi.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         final ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent.connect(true);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test bringing up unvalidated cellular.
         // Expect it to be torn down because it could never be the highest scoring network
         // satisfying the default request even if it validated.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(false);
-        mCellNetworkAgent.expectDisconnected();
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(false);
+        mCellAgent.expectDisconnected();
         verifyActiveNetwork(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.disconnect();
-        mWiFiNetworkAgent.expectDisconnected();
+        mWiFiAgent.disconnect();
+        mWiFiAgent.expectDisconnected();
     }
 
     // TODO : migrate to @Parameterized
@@ -2869,37 +3098,37 @@
             final boolean cellRadioTimesharingCapable) throws Exception {
         mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
         // Test bringing up validated cellular.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        ExpectedBroadcast b = registerConnectivityBroadcast(1);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        ExpectedBroadcast b = expectConnectivityAction(1);
+        mCellAgent.connect(true);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         // Test bringing up validated WiFi.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        b = registerConnectivityBroadcast(2);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        b = expectConnectivityAction(2);
+        mWiFiAgent.connect(true);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Reevaluate WiFi (it'll instantly fail DNS).
-        b = registerConnectivityBroadcast(2);
-        assertTrue(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
+        b = expectConnectivityAction(2);
+        assertTrue(mCm.getNetworkCapabilities(mWiFiAgent.getNetwork()).hasCapability(
                 NET_CAPABILITY_VALIDATED));
-        mCm.reportBadNetwork(mWiFiNetworkAgent.getNetwork());
+        mCm.reportBadNetwork(mWiFiAgent.getNetwork());
         // Should quickly fall back to Cellular.
         b.expectBroadcast();
-        assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
+        assertFalse(mCm.getNetworkCapabilities(mWiFiAgent.getNetwork()).hasCapability(
                 NET_CAPABILITY_VALIDATED));
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         // Reevaluate cellular (it'll instantly fail DNS).
-        b = registerConnectivityBroadcast(2);
-        assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability(
+        b = expectConnectivityAction(2);
+        assertTrue(mCm.getNetworkCapabilities(mCellAgent.getNetwork()).hasCapability(
                 NET_CAPABILITY_VALIDATED));
-        mCm.reportBadNetwork(mCellNetworkAgent.getNetwork());
+        mCm.reportBadNetwork(mCellAgent.getNetwork());
         // Should quickly fall back to WiFi.
         b.expectBroadcast();
-        assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability(
+        assertFalse(mCm.getNetworkCapabilities(mCellAgent.getNetwork()).hasCapability(
                 NET_CAPABILITY_VALIDATED));
-        assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
+        assertFalse(mCm.getNetworkCapabilities(mWiFiAgent.getNetwork()).hasCapability(
                 NET_CAPABILITY_VALIDATED));
         verifyActiveNetwork(TRANSPORT_WIFI);
     }
@@ -2920,25 +3149,25 @@
             final boolean cellRadioTimesharingCapable) throws Exception {
         mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
         // Test bringing up unvalidated WiFi.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        ExpectedBroadcast b = registerConnectivityBroadcast(1);
-        mWiFiNetworkAgent.connect(false);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        ExpectedBroadcast b = expectConnectivityAction(1);
+        mWiFiAgent.connect(false);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test bringing up validated cellular.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        b = registerConnectivityBroadcast(2);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        b = expectConnectivityAction(2);
+        mCellAgent.connect(true);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         // Reevaluate cellular (it'll instantly fail DNS).
-        b = registerConnectivityBroadcast(2);
-        assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability(
+        b = expectConnectivityAction(2);
+        assertTrue(mCm.getNetworkCapabilities(mCellAgent.getNetwork()).hasCapability(
                 NET_CAPABILITY_VALIDATED));
-        mCm.reportBadNetwork(mCellNetworkAgent.getNetwork());
+        mCm.reportBadNetwork(mCellAgent.getNetwork());
         // Should quickly fall back to WiFi.
         b.expectBroadcast();
-        assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability(
+        assertFalse(mCm.getNetworkCapabilities(mCellAgent.getNetwork()).hasCapability(
                 NET_CAPABILITY_VALIDATED));
         verifyActiveNetwork(TRANSPORT_WIFI);
     }
@@ -2957,18 +3186,16 @@
      */
     private class TestNetworkCallback extends TestableNetworkCallback {
         TestNetworkCallback() {
-            super(TEST_CALLBACK_TIMEOUT_MS);
-        }
-
-        @Override
-        public void assertNoCallback() {
-            // TODO: better support this use case in TestableNetworkCallback
-            waitForIdle();
-            assertNoCallback(0 /* timeout */);
+            // In the context of this test, the testable network callbacks should use waitForIdle
+            // before calling assertNoCallback in an effort to detect issues where a callback is
+            // not yet sent but a message currently in the queue of a handler will cause it to
+            // be sent soon.
+            super(TEST_CALLBACK_TIMEOUT_MS, TEST_CALLBACK_TIMEOUT_MS,
+                    ConnectivityServiceTest.this::waitForIdle);
         }
 
         public CallbackEntry.Losing expectLosing(final HasNetwork n, final long timeoutMs) {
-            final CallbackEntry.Losing losing = expect(CallbackEntry.LOSING, n, timeoutMs);
+            final CallbackEntry.Losing losing = expect(LOSING, n, timeoutMs);
             final int maxMsToLive = losing.getMaxMsToLive();
             if (maxMsToLive < 0 || maxMsToLive > mService.mLingerDelayMs) {
                 // maxMsToLive is the value that was received in the onLosing callback. That must
@@ -2990,27 +3217,32 @@
 
     // Can't be part of TestNetworkCallback because "cannot be declared static; static methods can
     // only be declared in a static or top level type".
+    static void assertNoCallbacks(final long timeoutMs, TestNetworkCallback ... callbacks) {
+        for (TestNetworkCallback c : callbacks) {
+            c.assertNoCallback(timeoutMs);
+        }
+    }
+
     static void assertNoCallbacks(TestNetworkCallback ... callbacks) {
         for (TestNetworkCallback c : callbacks) {
-            c.assertNoCallback();
+            c.assertNoCallback(); // each callback uses its own timeout
         }
     }
 
     static void expectOnLost(TestNetworkAgentWrapper network, TestNetworkCallback ... callbacks) {
         for (TestNetworkCallback c : callbacks) {
-            c.expect(CallbackEntry.LOST, network);
+            c.expect(LOST, network);
         }
     }
 
     static void expectAvailableCallbacksUnvalidatedWithSpecifier(TestNetworkAgentWrapper network,
             NetworkSpecifier specifier, TestNetworkCallback ... callbacks) {
         for (TestNetworkCallback c : callbacks) {
-            c.expect(CallbackEntry.AVAILABLE, network);
-            c.expectCapabilitiesThat(network, (nc) ->
-                    !nc.hasCapability(NET_CAPABILITY_VALIDATED)
-                            && Objects.equals(specifier, nc.getNetworkSpecifier()));
-            c.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, network);
-            c.expect(CallbackEntry.BLOCKED_STATUS, network);
+            c.expect(AVAILABLE, network);
+            c.expectCaps(network, cb -> !cb.hasCapability(NET_CAPABILITY_VALIDATED)
+                    && Objects.equals(specifier, cb.getNetworkSpecifier()));
+            c.expect(LINK_PROPERTIES_CHANGED, network);
+            c.expect(BLOCKED_STATUS, network);
         }
     }
 
@@ -3020,12 +3252,12 @@
         final NetworkRequest wifiRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_WIFI).build();
         mCm.requestNetwork(wifiRequest, cb);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         // Updating the score triggers a rematch.
-        mWiFiNetworkAgent.setScore(new NetworkScore.Builder().build());
+        mWiFiAgent.setScore(new NetworkScore.Builder().build());
         cb.assertNoCallback();
-        mWiFiNetworkAgent.connect(false);
-        cb.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mWiFiAgent.connect(false);
+        cb.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         cb.assertNoCallback();
         mCm.unregisterNetworkCallback(cb);
     }
@@ -3036,13 +3268,13 @@
         final NetworkRequest wifiRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_WIFI).build();
         mCm.registerNetworkCallback(wifiRequest, cb);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        final NetworkCapabilities nc = mWiFiNetworkAgent.getNetworkCapabilities();
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        final NetworkCapabilities nc = mWiFiAgent.getNetworkCapabilities();
         nc.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
-        mWiFiNetworkAgent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+        mWiFiAgent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
         cb.assertNoCallback();
-        mWiFiNetworkAgent.connect(false);
-        cb.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mWiFiAgent.connect(false);
+        cb.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         final CallbackEntry found = CollectionUtils.findLast(cb.getHistory(),
                 it -> it instanceof CallbackEntry.CapabilitiesChanged);
         assertTrue(((CallbackEntry.CapabilitiesChanged) found).getCaps()
@@ -3067,77 +3299,81 @@
         mCm.registerNetworkCallback(cellRequest, cellNetworkCallback);
 
         // Test unvalidated networks
-        ExpectedBroadcast b = registerConnectivityBroadcast(1);
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(false);
-        genericNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
-        cellNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        ExpectedBroadcast b = expectConnectivityAction(1);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(false);
+        genericNetworkCallback.expectAvailableCallbacksUnvalidated(mCellAgent);
+        cellNetworkCallback.expectAvailableCallbacksUnvalidated(mCellAgent);
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
         b.expectBroadcast();
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
 
         // This should not trigger spurious onAvailable() callbacks, b/21762680.
-        mCellNetworkAgent.adjustScore(-1);
+        mCellAgent.adjustScore(-1);
         waitForIdle();
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
 
-        b = registerConnectivityBroadcast(2);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);
-        genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        b = expectConnectivityAction(2);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);
+        genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
         b.expectBroadcast();
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
 
-        b = registerConnectivityBroadcast(2);
-        mWiFiNetworkAgent.disconnect();
-        genericNetworkCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        wifiNetworkCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        b = expectConnectivityAction(2);
+        mWiFiAgent.disconnect();
+        genericNetworkCallback.expect(CallbackEntry.LOST, mWiFiAgent);
+        wifiNetworkCallback.expect(CallbackEntry.LOST, mWiFiAgent);
         cellNetworkCallback.assertNoCallback();
         b.expectBroadcast();
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
 
-        b = registerConnectivityBroadcast(1);
-        mCellNetworkAgent.disconnect();
-        genericNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
-        cellNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        b = expectConnectivityAction(1);
+        mCellAgent.disconnect();
+        genericNetworkCallback.expect(CallbackEntry.LOST, mCellAgent);
+        cellNetworkCallback.expect(CallbackEntry.LOST, mCellAgent);
         b.expectBroadcast();
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
 
         // Test validated networks
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        genericNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        genericNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
 
         // This should not trigger spurious onAvailable() callbacks, b/21762680.
-        mCellNetworkAgent.adjustScore(-1);
+        mCellAgent.adjustScore(-1);
         waitForIdle();
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
-        genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        genericNetworkCallback.expectLosing(mCellNetworkAgent);
-        genericNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        wifiNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
-        cellNetworkCallback.expectLosing(mCellNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
+        genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        genericNetworkCallback.expectLosing(mCellAgent);
+        genericNetworkCallback.expectCaps(mWiFiAgent,
+                c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
+        wifiNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiAgent);
+        cellNetworkCallback.expectLosing(mCellAgent);
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
+        // Cell will disconnect after the lingering period. Before that elapses check that
+        // there have been no callbacks.
+        assertNoCallbacks(0 /* timeoutMs */,
+                genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
+
+        mWiFiAgent.disconnect();
+        genericNetworkCallback.expect(LOST, mWiFiAgent);
+        wifiNetworkCallback.expect(LOST, mWiFiAgent);
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
 
-        mWiFiNetworkAgent.disconnect();
-        genericNetworkCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        wifiNetworkCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
-
-        mCellNetworkAgent.disconnect();
-        genericNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
-        cellNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        mCellAgent.disconnect();
+        genericNetworkCallback.expect(LOST, mCellAgent);
+        cellNetworkCallback.expect(LOST, mCellAgent);
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
     }
 
@@ -3149,10 +3385,10 @@
         mCm.registerNetworkCallback(wifiRequest, callback);
         mCm.registerDefaultNetworkCallback(defaultCallback);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
 
         final LinkProperties newLp = new LinkProperties();
         final Uri capportUrl = Uri.parse("https://capport.example.com/api");
@@ -3161,20 +3397,20 @@
 
         final Uri expectedCapportUrl = sanitized ? null : capportUrl;
         newLp.setCaptivePortalApiUrl(capportUrl);
-        mWiFiNetworkAgent.sendLinkProperties(newLp);
-        callback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp ->
-                Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl()));
-        defaultCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp ->
-                Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl()));
+        mWiFiAgent.sendLinkProperties(newLp);
+        callback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent, cb ->
+                Objects.equals(expectedCapportUrl, cb.getLp().getCaptivePortalApiUrl()));
+        defaultCallback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent, cb ->
+                Objects.equals(expectedCapportUrl, cb.getLp().getCaptivePortalApiUrl()));
 
         final CaptivePortalData expectedCapportData = sanitized ? null : capportData;
-        mWiFiNetworkAgent.notifyCapportApiDataChanged(capportData);
-        callback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp ->
-                Objects.equals(expectedCapportData, lp.getCaptivePortalData()));
-        defaultCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp ->
-                Objects.equals(expectedCapportData, lp.getCaptivePortalData()));
+        mWiFiAgent.notifyCapportApiDataChanged(capportData);
+        callback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent, cb ->
+                Objects.equals(expectedCapportData, cb.getLp().getCaptivePortalData()));
+        defaultCallback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent, cb ->
+                Objects.equals(expectedCapportData, cb.getLp().getCaptivePortalData()));
 
-        final LinkProperties lp = mCm.getLinkProperties(mWiFiNetworkAgent.getNetwork());
+        final LinkProperties lp = mCm.getLinkProperties(mWiFiAgent.getNetwork());
         assertEquals(expectedCapportUrl, lp.getCaptivePortalApiUrl());
         assertEquals(expectedCapportData, lp.getCaptivePortalData());
     }
@@ -3209,19 +3445,18 @@
         final int originalOwnerUid = Process.myUid();
         ncTemplate.setOwnerUid(originalOwnerUid);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, new LinkProperties(),
-                ncTemplate);
-        mWiFiNetworkAgent.connect(false);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, new LinkProperties(), ncTemplate);
+        mWiFiAgent.connect(false);
         waitForIdle();
 
-        // Send ConnectivityService an update to the mWiFiNetworkAgent's capabilities that changes
+        // Send ConnectivityService an update to the mWiFiAgent's capabilities that changes
         // the owner UID and an unrelated capability.
-        NetworkCapabilities agentCapabilities = mWiFiNetworkAgent.getNetworkCapabilities();
+        NetworkCapabilities agentCapabilities = mWiFiAgent.getNetworkCapabilities();
         assertEquals(originalOwnerUid, agentCapabilities.getOwnerUid());
         agentCapabilities.setOwnerUid(42);
         assertFalse(agentCapabilities.hasCapability(NET_CAPABILITY_NOT_CONGESTED));
         agentCapabilities.addCapability(NET_CAPABILITY_NOT_CONGESTED);
-        mWiFiNetworkAgent.setNetworkCapabilities(agentCapabilities, true);
+        mWiFiAgent.setNetworkCapabilities(agentCapabilities, true);
         waitForIdle();
 
         // Owner UIDs are not visible without location permission.
@@ -3229,7 +3464,7 @@
                 Manifest.permission.ACCESS_FINE_LOCATION);
 
         // Check that the capability change has been applied but the owner UID is not modified.
-        NetworkCapabilities nc = mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork());
+        NetworkCapabilities nc = mCm.getNetworkCapabilities(mWiFiAgent.getNetwork());
         assertEquals(originalOwnerUid, nc.getOwnerUid());
         assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_CONGESTED));
     }
@@ -3239,8 +3474,10 @@
         // This test would be flaky with the default 120ms timer: that is short enough that
         // lingered networks are torn down before assertions can be run. We don't want to mock the
         // lingering timer to keep the WakeupMessage logic realistic: this has already proven useful
-        // in detecting races.
-        mService.mLingerDelayMs = 300;
+        // in detecting races. Furthermore, sometimes the test is running while Phenotype is running
+        // so hot that the test doesn't get the CPU for multiple hundreds of milliseconds, so this
+        // needs to be suitably long.
+        mService.mLingerDelayMs = 2_000;
 
         NetworkRequest request = new NetworkRequest.Builder()
                 .clearCapabilities().addCapability(NET_CAPABILITY_NOT_METERED)
@@ -3251,56 +3488,56 @@
         TestNetworkCallback defaultCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultCallback);
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mEthernetAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
 
-        mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
-        mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
-        mEthernetNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        mCellAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        mWiFiAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        mEthernetAgent.addCapability(NET_CAPABILITY_NOT_METERED);
 
-        mCellNetworkAgent.connect(true);
-        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        mCellAgent.connect(true);
+        callback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        defaultCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent.connect(true);
         // We get AVAILABLE on wifi when wifi connects and satisfies our unmetered request.
         // We then get LOSING when wifi validates and cell is outscored.
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         // TODO: Investigate sending validated before losing.
-        callback.expectLosing(mCellNetworkAgent);
-        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        callback.expectLosing(mCellAgent);
+        callback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        mEthernetNetworkAgent.connect(true);
-        callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent);
+        mEthernetAgent.connect(true);
+        callback.expectAvailableCallbacksUnvalidated(mEthernetAgent);
         // TODO: Investigate sending validated before losing.
-        callback.expectLosing(mWiFiNetworkAgent);
-        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
-        defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
-        assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        callback.expectLosing(mWiFiAgent);
+        callback.expectCaps(mEthernetAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetAgent);
+        assertEquals(mEthernetAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        mEthernetNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mEthernetNetworkAgent);
-        defaultCallback.expect(CallbackEntry.LOST, mEthernetNetworkAgent);
-        defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
+        mEthernetAgent.disconnect();
+        callback.expect(LOST, mEthernetAgent);
+        defaultCallback.expect(LOST, mEthernetAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mWiFiAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         for (int i = 0; i < 4; i++) {
             TestNetworkAgentWrapper oldNetwork, newNetwork;
             if (i % 2 == 0) {
-                mWiFiNetworkAgent.adjustScore(-15);
-                oldNetwork = mWiFiNetworkAgent;
-                newNetwork = mCellNetworkAgent;
+                mWiFiAgent.adjustScore(-15);
+                oldNetwork = mWiFiAgent;
+                newNetwork = mCellAgent;
             } else {
-                mWiFiNetworkAgent.adjustScore(15);
-                oldNetwork = mCellNetworkAgent;
-                newNetwork = mWiFiNetworkAgent;
+                mWiFiAgent.adjustScore(15);
+                oldNetwork = mCellAgent;
+                newNetwork = mWiFiAgent;
 
             }
             callback.expectLosing(oldNetwork);
@@ -3309,28 +3546,28 @@
             defaultCallback.expectAvailableCallbacksValidated(newNetwork);
             assertEquals(newNetwork.getNetwork(), mCm.getActiveNetwork());
         }
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Verify that if a network no longer satisfies a request, we send LOST and not LOSING, even
         // if the network is still up.
-        mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+        mWiFiAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
         // We expect a notification about the capabilities change, and nothing else.
-        defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent);
+        defaultCallback.expectCaps(mWiFiAgent, c -> !c.hasCapability(NET_CAPABILITY_NOT_METERED));
         defaultCallback.assertNoCallback();
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        callback.expect(LOST, mWiFiAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Wifi no longer satisfies our listen, which is for an unmetered network.
         // But because its score is 55, it's still up (and the default network).
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Disconnect our test networks.
-        mWiFiNetworkAgent.disconnect();
-        defaultCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        mWiFiAgent.disconnect();
+        defaultCallback.expect(LOST, mWiFiAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mCellAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
-        mCellNetworkAgent.disconnect();
-        defaultCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        mCellAgent.disconnect();
+        defaultCallback.expect(LOST, mCellAgent);
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
 
@@ -3344,64 +3581,64 @@
 
         mCm.registerNetworkCallback(request, callback);
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(false);   // Score: 10
-        callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
-        defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(false);   // Score: 10
+        callback.expectAvailableCallbacksUnvalidated(mCellAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mCellAgent);
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi with a score of 20.
         // Cell stays up because it would satisfy the default request if it validated.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);   // Score: 20
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);   // Score: 20
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        mWiFiNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        defaultCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        mWiFiAgent.disconnect();
+        callback.expect(LOST, mWiFiAgent);
+        defaultCallback.expect(LOST, mWiFiAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mCellAgent);
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi, then validate it. Previous versions would immediately tear down cell, but
         // it's arguably correct to linger it, since it was the default network before it validated.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         // TODO: Investigate sending validated before losing.
-        callback.expectLosing(mCellNetworkAgent);
-        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        callback.expectLosing(mCellAgent);
+        callback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
+        defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiAgent);
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        mWiFiNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        defaultCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
-        mCellNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mCellNetworkAgent);
-        defaultCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        mWiFiAgent.disconnect();
+        callback.expect(LOST, mWiFiAgent);
+        defaultCallback.expect(LOST, mWiFiAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mCellAgent);
+        mCellAgent.disconnect();
+        callback.expect(LOST, mCellAgent);
+        defaultCallback.expect(LOST, mCellAgent);
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
 
         // If a network is lingering, and we add and remove a request from it, resume lingering.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        callback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        defaultCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
-        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         // TODO: Investigate sending validated before losing.
-        callback.expectLosing(mCellNetworkAgent);
-        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        callback.expectLosing(mCellAgent);
+        callback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         NetworkRequest cellRequest = new NetworkRequest.Builder()
@@ -3411,14 +3648,14 @@
         // TODO: should this cause an AVAILABLE callback, to indicate that the network is no longer
         // lingering?
         mCm.unregisterNetworkCallback(noopCallback);
-        callback.expectLosing(mCellNetworkAgent);
+        callback.expectLosing(mCellAgent);
 
         // Similar to the above: lingering can start even after the lingered request is removed.
         // Disconnect wifi and switch to cell.
-        mWiFiNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        defaultCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        mWiFiAgent.disconnect();
+        callback.expect(LOST, mWiFiAgent);
+        defaultCallback.expect(LOST, mWiFiAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mCellAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Cell is now the default network. Pin it with a cell-specific request.
@@ -3426,44 +3663,44 @@
         mCm.requestNetwork(cellRequest, noopCallback);
 
         // Now connect wifi, and expect it to become the default network.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
-        callback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
-        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
+        callback.expectAvailableThenValidatedCallbacks(mWiFiAgent);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
         // The default request is lingering on cell, but nothing happens to cell, and we send no
         // callbacks for it, because it's kept up by cellRequest.
         callback.assertNoCallback();
         // Now unregister cellRequest and expect cell to start lingering.
         mCm.unregisterNetworkCallback(noopCallback);
-        callback.expectLosing(mCellNetworkAgent);
+        callback.expectLosing(mCellAgent);
 
         // Let linger run its course.
-        callback.assertNoCallback();
+        callback.assertNoCallback(0 /* timeoutMs */);
         final int lingerTimeoutMs = mService.mLingerDelayMs + mService.mLingerDelayMs / 4;
-        callback.expect(CallbackEntry.LOST, mCellNetworkAgent, lingerTimeoutMs);
+        callback.expect(LOST, mCellAgent, lingerTimeoutMs);
 
         // Register a TRACK_DEFAULT request and check that it does not affect lingering.
         TestNetworkCallback trackDefaultCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(trackDefaultCallback);
-        trackDefaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
-        mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
-        mEthernetNetworkAgent.connect(true);
-        callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent);
-        callback.expectLosing(mWiFiNetworkAgent);
-        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
-        trackDefaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
-        defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
+        trackDefaultCallback.expectAvailableCallbacksValidated(mWiFiAgent);
+        mEthernetAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
+        mEthernetAgent.connect(true);
+        callback.expectAvailableCallbacksUnvalidated(mEthernetAgent);
+        callback.expectLosing(mWiFiAgent);
+        callback.expectCaps(mEthernetAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
+        trackDefaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetAgent);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Let linger run its course.
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent, lingerTimeoutMs);
+        callback.expect(LOST, mWiFiAgent, lingerTimeoutMs);
 
         // Clean up.
-        mEthernetNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mEthernetNetworkAgent);
-        defaultCallback.expect(CallbackEntry.LOST, mEthernetNetworkAgent);
-        trackDefaultCallback.expect(CallbackEntry.LOST, mEthernetNetworkAgent);
+        mEthernetAgent.disconnect();
+        callback.expect(LOST, mEthernetAgent);
+        defaultCallback.expect(LOST, mEthernetAgent);
+        trackDefaultCallback.expect(LOST, mEthernetAgent);
 
         mCm.unregisterNetworkCallback(callback);
         mCm.unregisterNetworkCallback(defaultCallback);
@@ -3499,19 +3736,19 @@
         TestNetworkCallback defaultCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultCallback);
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
 
-        mCellNetworkAgent.connect(true);
-        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent.connect(true);
+        callback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        defaultCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
 
         // Wifi comes up and cell lingers.
-        mWiFiNetworkAgent.connect(true);
-        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectLosing(mCellNetworkAgent);
-        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        mWiFiAgent.connect(true);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        callback.expectLosing(mCellAgent);
+        callback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
 
         // File a request for cellular, then release it.
         NetworkRequest cellRequest = new NetworkRequest.Builder()
@@ -3519,13 +3756,13 @@
         NetworkCallback noopCallback = new NetworkCallback();
         mCm.requestNetwork(cellRequest, noopCallback);
         mCm.unregisterNetworkCallback(noopCallback);
-        callback.expectLosing(mCellNetworkAgent);
+        callback.expectLosing(mCellAgent);
 
         // Let linger run its course.
         callback.assertNoCallback();
         final int lingerTimeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
-        callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent,
-                lingerTimeoutMs);
+        callback.expectCaps(mCellAgent, lingerTimeoutMs,
+                c -> !c.hasCapability(NET_CAPABILITY_FOREGROUND));
 
         // Clean up.
         mCm.unregisterNetworkCallback(defaultCallback);
@@ -3580,7 +3817,7 @@
 
     private void expectDisconnectAndClearNotifications(TestNetworkCallback callback,
             TestNetworkAgentWrapper agent, NotificationType type) {
-        callback.expect(CallbackEntry.LOST, agent);
+        callback.expect(LOST, agent);
         expectClearNotification(agent, type);
     }
 
@@ -3651,7 +3888,13 @@
         final NetworkAgentWrapper.Callbacks callbacks = new NetworkAgentWrapper.Callbacks(
                 onNetworkCreated, onNetworkUnwanted, onNetworkDisconnected);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, callbacks);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, callbacks);
+
+        if (mService.shouldCreateNetworksImmediately()) {
+            assertEquals("onNetworkCreated", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } else {
+            assertNull(eventOrder.poll());
+        }
 
         // Connect a network, and file a request for it after it has come up, to ensure the nascent
         // timer is cleared and the test does not have to wait for it. Filing the request after the
@@ -3659,13 +3902,18 @@
         // nascent timer if the first request satisfied by the network was filed before the network
         // connected.
         // TODO: fix this bug, file the request before connecting, and remove the waitForIdle.
-        mWiFiNetworkAgent.connectWithoutInternet();
-        waitForIdle();
+        mWiFiAgent.connectWithoutInternet();
+        if (!mService.shouldCreateNetworksImmediately()) {
+            assertEquals("onNetworkCreated", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } else {
+            waitForIdle();
+            assertNull(eventOrder.poll());
+        }
         mCm.requestNetwork(request, callback);
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
 
         // Set teardown delay and make sure CS has processed it.
-        mWiFiNetworkAgent.getNetworkAgent().setTeardownDelayMillis(300);
+        mWiFiAgent.getNetworkAgent().setTeardownDelayMillis(300);
         waitForIdle();
 
         // Post the duringTeardown lambda to the handler so it fires while teardown is in progress.
@@ -3673,11 +3921,10 @@
         // down the network and started the teardown timer, and short enough that the lambda is
         // scheduled to run before the teardown timer.
         final Handler h = new Handler(mCsHandlerThread.getLooper());
-        h.postDelayed(() -> duringTeardown.accept(mWiFiNetworkAgent.getNetwork()), 150);
+        h.postDelayed(() -> duringTeardown.accept(mWiFiAgent.getNetwork()), 150);
 
         // Disconnect the network and check that events happened in the right order.
         mCm.unregisterNetworkCallback(callback);
-        assertEquals("onNetworkCreated", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         assertEquals("onNetworkUnwanted", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         assertEquals("timePasses", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         assertEquals("onNetworkDisconnected", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
@@ -3687,113 +3934,118 @@
 
     @Test
     public void testExplicitlySelected() throws Exception {
-        NetworkRequest request = new NetworkRequest.Builder()
+        final NetworkRequest request = new NetworkRequest.Builder()
                 .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
                 .build();
-        TestNetworkCallback callback = new TestNetworkCallback();
+        final TestNetworkCallback callback = new TestNetworkCallback();
         mCm.registerNetworkCallback(request, callback);
 
-        // Bring up validated cell.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        // Bring up validated cell
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        callback.expectAvailableThenValidatedCallbacks(mCellAgent);
 
         // Bring up unvalidated wifi with explicitlySelected=true.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.explicitlySelected(true, false);
-        mWiFiNetworkAgent.connect(false);
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.explicitlySelected(true, false);
+        mWiFiAgent.connect(false);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
 
         // Cell remains the default.
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Expect a high-priority NO_INTERNET notification.
-        expectUnvalidationCheckWillNotify(mWiFiNetworkAgent, NotificationType.NO_INTERNET);
+        expectUnvalidationCheckWillNotify(mWiFiAgent, NotificationType.NO_INTERNET);
 
         // Lower WiFi's score to lower than cell, and check that it doesn't disconnect because
         // it's explicitly selected.
-        mWiFiNetworkAgent.adjustScore(-40);
-        mWiFiNetworkAgent.adjustScore(40);
+        mWiFiAgent.adjustScore(-40);
+        mWiFiAgent.adjustScore(40);
         callback.assertNoCallback();
 
         // If the user chooses yes on the "No Internet access, stay connected?" dialog, we switch to
         // wifi even though it's unvalidated.
-        mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), true, false);
-        callback.expectLosing(mCellNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        mCm.setAcceptUnvalidated(mWiFiAgent.getNetwork(), true, false);
+        callback.expectLosing(mCellAgent);
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Disconnect wifi, and then reconnect, again with explicitlySelected=true.
-        mWiFiNetworkAgent.disconnect();
-        expectDisconnectAndClearNotifications(callback, mWiFiNetworkAgent,
-                NotificationType.NO_INTERNET);
+        mWiFiAgent.disconnect();
+        expectDisconnectAndClearNotifications(callback, mWiFiAgent, NotificationType.NO_INTERNET);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.explicitlySelected(true, false);
-        mWiFiNetworkAgent.connect(false);
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.explicitlySelected(true, false);
+        mWiFiAgent.connect(false);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
 
         // Expect a high-priority NO_INTERNET notification.
-        expectUnvalidationCheckWillNotify(mWiFiNetworkAgent, NotificationType.NO_INTERNET);
+        expectUnvalidationCheckWillNotify(mWiFiAgent, NotificationType.NO_INTERNET);
 
         // If the user chooses no on the "No Internet access, stay connected?" dialog, we ask the
         // network to disconnect.
-        mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), false, false);
-        expectDisconnectAndClearNotifications(callback, mWiFiNetworkAgent,
-                NotificationType.NO_INTERNET);
+        mCm.setAcceptUnvalidated(mWiFiAgent.getNetwork(), false, false);
+        expectDisconnectAndClearNotifications(callback, mWiFiAgent, NotificationType.NO_INTERNET);
         reset(mNotificationManager);
 
         // Reconnect, again with explicitlySelected=true, but this time validate.
         // Expect no notifications.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.explicitlySelected(true, false);
-        mWiFiNetworkAgent.connect(true);
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectLosing(mCellNetworkAgent);
-        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
-        expectUnvalidationCheckWillNotNotify(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.explicitlySelected(true, false);
+        mWiFiAgent.connect(true);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        callback.expectLosing(mCellAgent);
+        callback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
+        expectUnvalidationCheckWillNotNotify(mWiFiAgent);
 
-        mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
-        mEthernetNetworkAgent.connect(true);
-        callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent);
-        callback.expectLosing(mWiFiNetworkAgent);
-        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
-        assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        // Now request cell so it doesn't disconnect during the test
+        final NetworkRequest cellRequest = new NetworkRequest.Builder()
+                .clearCapabilities().addTransportType(TRANSPORT_CELLULAR).build();
+        final TestNetworkCallback cellCallback = new TestNetworkCallback();
+        mCm.requestNetwork(cellRequest, cellCallback);
+
+        mEthernetAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
+        mEthernetAgent.connect(true);
+        callback.expectAvailableCallbacksUnvalidated(mEthernetAgent);
+        callback.expectLosing(mWiFiAgent);
+        callback.expectCaps(mEthernetAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
+        assertEquals(mEthernetAgent.getNetwork(), mCm.getActiveNetwork());
         callback.assertNoCallback();
 
         // Disconnect wifi, and then reconnect as if the user had selected "yes, don't ask again"
         // (i.e., with explicitlySelected=true and acceptUnvalidated=true). Expect to switch to
         // wifi immediately.
-        mWiFiNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.explicitlySelected(true, true);
-        mWiFiNetworkAgent.connect(false);
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectLosing(mEthernetNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
-        mEthernetNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mEthernetNetworkAgent);
-        expectUnvalidationCheckWillNotNotify(mWiFiNetworkAgent);
+        mWiFiAgent.disconnect();
+        callback.expect(LOST, mWiFiAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.explicitlySelected(true, true);
+        mWiFiAgent.connect(false);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        callback.expectLosing(mEthernetAgent);
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
+        mEthernetAgent.disconnect();
+        callback.expect(LOST, mEthernetAgent);
+        expectUnvalidationCheckWillNotNotify(mWiFiAgent);
 
         // Disconnect and reconnect with explicitlySelected=false and acceptUnvalidated=true.
         // Check that the network is not scored specially and that the device prefers cell data.
-        mWiFiNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        mWiFiAgent.disconnect();
+        callback.expect(LOST, mWiFiAgent);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.explicitlySelected(false, true);
-        mWiFiNetworkAgent.connect(false);
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
-        expectUnvalidationCheckWillNotNotify(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.explicitlySelected(false, true);
+        mWiFiAgent.connect(false);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
+        expectUnvalidationCheckWillNotNotify(mWiFiAgent);
 
         // Clean up.
-        mWiFiNetworkAgent.disconnect();
-        mCellNetworkAgent.disconnect();
+        mWiFiAgent.disconnect();
+        mCellAgent.disconnect();
 
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        callback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        callback.expect(LOST, mWiFiAgent);
+        callback.expect(LOST, mCellAgent);
+        mCm.unregisterNetworkCallback(cellCallback);
     }
 
     private void doTestFirstEvaluation(
@@ -3807,36 +4059,36 @@
         TestNetworkCallback callback = new TestNetworkCallback();
         mCm.registerNetworkCallback(request, callback);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        doConnect.accept(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        doConnect.accept(mWiFiAgent);
         // Expect the available callbacks, but don't require specific values for their arguments
         // since this method doesn't know how the network was connected.
-        callback.expect(CallbackEntry.AVAILABLE, mWiFiNetworkAgent);
-        callback.expect(CallbackEntry.NETWORK_CAPS_UPDATED, mWiFiNetworkAgent);
-        callback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mWiFiNetworkAgent);
-        callback.expect(CallbackEntry.BLOCKED_STATUS, mWiFiNetworkAgent);
+        callback.expect(AVAILABLE, mWiFiAgent);
+        callback.expect(NETWORK_CAPS_UPDATED, mWiFiAgent);
+        callback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent);
+        callback.expect(BLOCKED_STATUS, mWiFiAgent);
         if (waitForSecondCaps) {
             // This is necessary because of b/245893397, the same bug that happens where we use
             // expectAvailableDoubleValidatedCallbacks.
-            callback.expect(CallbackEntry.NETWORK_CAPS_UPDATED, mWiFiNetworkAgent);
+            callback.expect(NETWORK_CAPS_UPDATED, mWiFiAgent);
         }
         final NetworkAgentInfo nai =
-                mService.getNetworkAgentInfoForNetwork(mWiFiNetworkAgent.getNetwork());
+                mService.getNetworkAgentInfoForNetwork(mWiFiAgent.getNetwork());
         final long firstEvaluation = nai.getFirstEvaluationConcludedTime();
         if (evaluatedByValidation) {
             assertNotEquals(0L, firstEvaluation);
         } else {
             assertEquals(0L, firstEvaluation);
         }
-        mService.scheduleEvaluationTimeout(mWiFiNetworkAgent.getNetwork(), 0L /* timeout */);
+        mService.scheduleEvaluationTimeout(mWiFiAgent.getNetwork(), 0L /* timeout */);
         waitForIdle();
         if (evaluatedByValidation) {
             assertEquals(firstEvaluation, nai.getFirstEvaluationConcludedTime());
         } else {
             assertNotEquals(0L, nai.getFirstEvaluationConcludedTime());
         }
-        mWiFiNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        mWiFiAgent.disconnect();
+        callback.expect(LOST, mWiFiAgent);
 
         mCm.unregisterNetworkCallback(callback);
     }
@@ -4005,9 +4257,9 @@
 
     @Test
     public void testRegisterIgnoringScore() throws Exception {
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(90).build());
-        mWiFiNetworkAgent.connect(true /* validated */);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.setScore(new NetworkScore.Builder().setLegacyInt(90).build());
+        mWiFiAgent.connect(true /* validated */);
 
         // Make sure the factory sees the default network
         final NetworkCapabilities filter = new NetworkCapabilities();
@@ -4030,13 +4282,13 @@
         testFactoryAll.expectRequestAdd();
 
         // The legacy int will be ignored anyway, set the only other knob to true
-        mWiFiNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(110)
+        mWiFiAgent.setScore(new NetworkScore.Builder().setLegacyInt(110)
                 .setTransportPrimary(true).build());
 
         expectNoRequestChanged(testFactory); // still not seeing the request
         expectNoRequestChanged(testFactoryAll); // still seeing the request
 
-        mWiFiNetworkAgent.disconnect();
+        mWiFiAgent.disconnect();
     }
 
     @Test
@@ -4124,18 +4376,18 @@
     @Test
     public void testMMSonWiFi() throws Exception {
         // Test bringing up cellular without MMS NetworkRequest gets reaped
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
-        mCellNetworkAgent.connectWithoutInternet();
-        mCellNetworkAgent.expectDisconnected();
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.addCapability(NET_CAPABILITY_MMS);
+        mCellAgent.connectWithoutInternet();
+        mCellAgent.expectDisconnected();
         waitForIdle();
         assertEmpty(mCm.getAllNetworks());
         verifyNoNetwork();
 
         // Test bringing up validated WiFi.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         final ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent.connect(true);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_WIFI);
 
@@ -4146,24 +4398,24 @@
         mCm.requestNetwork(builder.build(), networkCallback);
 
         // Test bringing up unvalidated cellular with MMS
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
-        mCellNetworkAgent.connectWithoutInternet();
-        networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.addCapability(NET_CAPABILITY_MMS);
+        mCellAgent.connectWithoutInternet();
+        networkCallback.expectAvailableCallbacksUnvalidated(mCellAgent);
         verifyActiveNetwork(TRANSPORT_WIFI);
 
         // Test releasing NetworkRequest disconnects cellular with MMS
         mCm.unregisterNetworkCallback(networkCallback);
-        mCellNetworkAgent.expectDisconnected();
+        mCellAgent.expectDisconnected();
         verifyActiveNetwork(TRANSPORT_WIFI);
     }
 
     @Test
     public void testMMSonCell() throws Exception {
         // Test bringing up cellular without MMS
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         ExpectedBroadcast b = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED);
-        mCellNetworkAgent.connect(false);
+        mCellAgent.connect(false);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_CELLULAR);
 
@@ -4197,142 +4449,139 @@
         mCm.registerNetworkCallback(request, callback);
 
         // Bring up validated mobile data.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        callback.expectAvailableThenValidatedCallbacks(mCellAgent);
 
         // Bring up wifi with partial connectivity.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connectWithPartialConnectivity();
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connectWithPartialConnectivity();
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        callback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
 
         // Mobile data should be the default network.
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
         callback.assertNoCallback();
 
         // Expect a PARTIAL_CONNECTIVITY notification. The notification appears as soon as partial
         // connectivity is detected, and is low priority because the network was not explicitly
         // selected by the user. This happens if we reconnect to a network where the user previously
         // accepted partial connectivity without checking "always".
-        expectNotification(mWiFiNetworkAgent, NotificationType.PARTIAL_CONNECTIVITY);
+        expectNotification(mWiFiAgent, NotificationType.PARTIAL_CONNECTIVITY);
 
         // With HTTPS probe disabled, NetworkMonitor should pass the network validation with http
         // probe.
-        mWiFiNetworkAgent.setNetworkPartialValid(false /* isStrictMode */);
+        mWiFiAgent.setNetworkPartialValid(false /* privateDnsProbeSent */);
         // If the user chooses yes to use this partial connectivity wifi, switch the default
         // network to wifi and check if wifi becomes valid or not.
-        mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */,
+        mCm.setAcceptPartialConnectivity(mWiFiAgent.getNetwork(), true /* accept */,
                 false /* always */);
         // If user accepts partial connectivity network,
         // NetworkMonitor#setAcceptPartialConnectivity() should be called too.
         waitForIdle();
-        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
+        verify(mWiFiAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
 
         // Need a trigger point to let NetworkMonitor tell ConnectivityService that the network is
         // validated.
-        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
-        callback.expectLosing(mCellNetworkAgent);
-        NetworkCapabilities nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED,
-                mWiFiNetworkAgent);
+        mCm.reportNetworkConnectivity(mWiFiAgent.getNetwork(), true);
+        callback.expectLosing(mCellAgent);
+        NetworkCapabilities nc =
+                callback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
         assertTrue(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Once the network validates, the notification disappears.
-        expectClearNotification(mWiFiNetworkAgent, NotificationType.PARTIAL_CONNECTIVITY);
+        expectClearNotification(mWiFiAgent, NotificationType.PARTIAL_CONNECTIVITY);
 
         // Disconnect and reconnect wifi with partial connectivity again.
-        mWiFiNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        mWiFiAgent.disconnect();
+        callback.expect(LOST, mWiFiAgent);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connectWithPartialConnectivity();
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connectWithPartialConnectivity();
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        callback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
 
         // Mobile data should be the default network.
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
         waitForIdle();
 
         // Expect a low-priority PARTIAL_CONNECTIVITY notification as soon as partial connectivity
         // is detected.
-        expectNotification(mWiFiNetworkAgent, NotificationType.PARTIAL_CONNECTIVITY);
+        expectNotification(mWiFiAgent, NotificationType.PARTIAL_CONNECTIVITY);
 
         // If the user chooses no, disconnect wifi immediately.
-        mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), false /* accept */,
+        mCm.setAcceptPartialConnectivity(mWiFiAgent.getNetwork(), false /* accept */,
                 false /* always */);
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        expectClearNotification(mWiFiNetworkAgent, NotificationType.PARTIAL_CONNECTIVITY);
+        callback.expect(LOST, mWiFiAgent);
+        expectClearNotification(mWiFiAgent, NotificationType.PARTIAL_CONNECTIVITY);
         reset(mNotificationManager);
 
         // If the user accepted partial connectivity before, and the device connects to that network
         // again, but now the network has full connectivity, then the network shouldn't contain
         // NET_CAPABILITY_PARTIAL_CONNECTIVITY.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         // acceptUnvalidated is also used as setting for accepting partial networks.
-        mWiFiNetworkAgent.explicitlySelected(true /* explicitlySelected */,
-                true /* acceptUnvalidated */);
-        mWiFiNetworkAgent.connect(true);
-        expectUnvalidationCheckWillNotNotify(mWiFiNetworkAgent);
+        mWiFiAgent.explicitlySelected(true /* explicitlySelected */, true /* acceptUnvalidated */);
+        mWiFiAgent.connect(true);
+        expectUnvalidationCheckWillNotNotify(mWiFiAgent);
 
         // If user accepted partial connectivity network before,
         // NetworkMonitor#setAcceptPartialConnectivity() will be called in
         // ConnectivityService#updateNetworkInfo().
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
-        callback.expectLosing(mCellNetworkAgent);
-        nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        verify(mWiFiAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
+        callback.expectLosing(mCellAgent);
+        nc = callback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
         assertFalse(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
 
         // Wifi should be the default network.
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
-        mWiFiNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
+        mWiFiAgent.disconnect();
+        callback.expect(LOST, mWiFiAgent);
 
         // The user accepted partial connectivity and selected "don't ask again". Now the user
         // reconnects to the partial connectivity network. Switch to wifi as soon as partial
         // connectivity is detected.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.explicitlySelected(true /* explicitlySelected */,
-                true /* acceptUnvalidated */);
-        mWiFiNetworkAgent.connectWithPartialConnectivity();
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.explicitlySelected(true /* explicitlySelected */, true /* acceptUnvalidated */);
+        mWiFiAgent.connectWithPartialConnectivity();
         // If user accepted partial connectivity network before,
         // NetworkMonitor#setAcceptPartialConnectivity() will be called in
         // ConnectivityService#updateNetworkInfo().
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
-        callback.expectLosing(mCellNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
-        callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
-        expectUnvalidationCheckWillNotNotify(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        verify(mWiFiAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
+        callback.expectLosing(mCellAgent);
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
+        callback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
+        expectUnvalidationCheckWillNotNotify(mWiFiAgent);
 
-        mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
+        mWiFiAgent.setNetworkValid(false /* privateDnsProbeSent */);
 
         // Need a trigger point to let NetworkMonitor tell ConnectivityService that the network is
         // validated.
-        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
-        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        mWiFiNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        mCm.reportNetworkConnectivity(mWiFiAgent.getNetwork(), true);
+        callback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
+        mWiFiAgent.disconnect();
+        callback.expect(LOST, mWiFiAgent);
 
         // If the user accepted partial connectivity, and the device auto-reconnects to the partial
         // connectivity network, it should contain both PARTIAL_CONNECTIVITY and VALIDATED.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.explicitlySelected(false /* explicitlySelected */,
-                true /* acceptUnvalidated */);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.explicitlySelected(false /* explicitlySelected */, true /* acceptUnvalidated */);
 
         // NetworkMonitor will immediately (once the HTTPS probe fails...) report the network as
         // valid, because ConnectivityService calls setAcceptPartialConnectivity before it calls
         // notifyNetworkConnected.
-        mWiFiNetworkAgent.connectWithPartialValidConnectivity(false /* isStrictMode */);
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
-        callback.expectLosing(mCellNetworkAgent);
-        callback.expectCapabilitiesWith(
-                NET_CAPABILITY_PARTIAL_CONNECTIVITY | NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        expectUnvalidationCheckWillNotNotify(mWiFiNetworkAgent);
-        mWiFiNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        mWiFiAgent.connectWithPartialValidConnectivity(false /* privateDnsProbeSent */);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        verify(mWiFiAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
+        callback.expectLosing(mCellAgent);
+        callback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)
+                && c.hasCapability(NET_CAPABILITY_VALIDATED));
+        expectUnvalidationCheckWillNotNotify(mWiFiAgent);
+        mWiFiAgent.disconnect();
+        callback.expect(LOST, mWiFiAgent);
         verifyNoMoreInteractions(mNotificationManager);
     }
 
@@ -4351,42 +4600,40 @@
 
         // Bring up a network with a captive portal.
         // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         String redirectUrl = "http://android.com/path";
-        mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl, false /* isStrictMode */);
-        wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), redirectUrl);
+        mWiFiAgent.connectWithCaptivePortal(redirectUrl, false /* privateDnsProbeSent */);
+        wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        assertEquals(mWiFiAgent.waitForRedirectUrl(), redirectUrl);
 
         // This is necessary because of b/245893397, the same bug that happens where we use
         // expectAvailableDoubleValidatedCallbacks.
         // TODO : fix b/245893397 and remove this.
-        wifiCallback.expectCapabilitiesWith(NET_CAPABILITY_CAPTIVE_PORTAL, mWiFiNetworkAgent);
+        wifiCallback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL));
 
         // Check that startCaptivePortalApp sends the expected command to NetworkMonitor.
-        mCm.startCaptivePortalApp(mWiFiNetworkAgent.getNetwork());
-        verify(mWiFiNetworkAgent.mNetworkMonitor, timeout(TIMEOUT_MS).times(1))
-                .launchCaptivePortalApp();
+        mCm.startCaptivePortalApp(mWiFiAgent.getNetwork());
+        verify(mWiFiAgent.mNetworkMonitor, timeout(TIMEOUT_MS).times(1)).launchCaptivePortalApp();
 
         // Report that the captive portal is dismissed with partial connectivity, and check that
         // callbacks are fired with PARTIAL and without CAPTIVE_PORTAL.
-        mWiFiNetworkAgent.setNetworkPartial();
-        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
+        mWiFiAgent.setNetworkPartial();
+        mCm.reportNetworkConnectivity(mWiFiAgent.getNetwork(), true);
         waitForIdle();
-        wifiCallback.expectCapabilitiesThat(
-                mWiFiNetworkAgent, nc ->
-                nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)
-                && !nc.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL));
+        wifiCallback.expectCaps(mWiFiAgent,
+                c -> c.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)
+                        && !c.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL));
 
         // Report partial connectivity is accepted.
-        mWiFiNetworkAgent.setNetworkPartialValid(false /* isStrictMode */);
-        mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */,
+        mWiFiAgent.setNetworkPartialValid(false /* privateDnsProbeSent */);
+        mCm.setAcceptPartialConnectivity(mWiFiAgent.getNetwork(), true /* accept */,
                 false /* always */);
         waitForIdle();
-        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
-        wifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
-        validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
-                mWiFiNetworkAgent);
+        mCm.reportNetworkConnectivity(mWiFiAgent.getNetwork(), true);
+        wifiCallback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
+        validatedCallback.expectAvailableCallbacksValidated(mWiFiAgent);
+        validatedCallback.expectCaps(mWiFiAgent,
+                c -> c.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
 
         mCm.unregisterNetworkCallback(wifiCallback);
         mCm.unregisterNetworkCallback(validatedCallback);
@@ -4406,39 +4653,39 @@
 
         // Bring up a network with a captive portal.
         // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         String firstRedirectUrl = "http://example.com/firstPath";
-        mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl, false /* isStrictMode */);
-        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl);
+        mWiFiAgent.connectWithCaptivePortal(firstRedirectUrl, false /* privateDnsProbeSent */);
+        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        assertEquals(mWiFiAgent.waitForRedirectUrl(), firstRedirectUrl);
 
         // Take down network.
         // Expect onLost callback.
-        mWiFiNetworkAgent.disconnect();
-        captivePortalCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        mWiFiAgent.disconnect();
+        captivePortalCallback.expect(LOST, mWiFiAgent);
 
         // Bring up a network with a captive portal.
         // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         String secondRedirectUrl = "http://example.com/secondPath";
-        mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl, false /* isStrictMode */);
-        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), secondRedirectUrl);
+        mWiFiAgent.connectWithCaptivePortal(secondRedirectUrl, false /* privateDnsProbeSent */);
+        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        assertEquals(mWiFiAgent.waitForRedirectUrl(), secondRedirectUrl);
 
         // Make captive portal disappear then revalidate.
         // Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL.
-        mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
-        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
-        captivePortalCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        mWiFiAgent.setNetworkValid(false /* privateDnsProbeSent */);
+        mCm.reportNetworkConnectivity(mWiFiAgent.getNetwork(), true);
+        captivePortalCallback.expect(LOST, mWiFiAgent);
 
         // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
-        validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
 
         // Break network connectivity.
         // Expect NET_CAPABILITY_VALIDATED onLost callback.
-        mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */);
-        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
-        validatedCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        mWiFiAgent.setNetworkInvalid(false /* invalidBecauseOfPrivateDns */);
+        mCm.reportNetworkConnectivity(mWiFiAgent.getNetwork(), false);
+        validatedCallback.expect(LOST, mWiFiAgent);
     }
 
     private Intent startCaptivePortalApp(TestNetworkAgentWrapper networkAgent) throws Exception {
@@ -4475,36 +4722,35 @@
         mCm.registerNetworkCallback(validatedRequest, validatedCallback);
 
         // Bring up wifi.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
-        validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
-        Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
+        validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
+        Network wifiNetwork = mWiFiAgent.getNetwork();
 
         // Check that calling startCaptivePortalApp does nothing.
         final int fastTimeoutMs = 100;
         mCm.startCaptivePortalApp(wifiNetwork);
         waitForIdle();
-        verify(mWiFiNetworkAgent.mNetworkMonitor, never()).launchCaptivePortalApp();
+        verify(mWiFiAgent.mNetworkMonitor, never()).launchCaptivePortalApp();
         mServiceContext.expectNoStartActivityIntent(fastTimeoutMs);
 
         // Turn into a captive portal.
-        mWiFiNetworkAgent.setNetworkPortal("http://example.com", false /* isStrictMode */);
+        mWiFiAgent.setNetworkPortal("http://example.com", false /* privateDnsProbeSent */);
         mCm.reportNetworkConnectivity(wifiNetwork, false);
-        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        validatedCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        validatedCallback.expect(LOST, mWiFiAgent);
         // This is necessary because of b/245893397, the same bug that happens where we use
         // expectAvailableDoubleValidatedCallbacks.
         // TODO : fix b/245893397 and remove this.
-        captivePortalCallback.expect(CallbackEntry.NETWORK_CAPS_UPDATED,
-                mWiFiNetworkAgent);
+        captivePortalCallback.expectCaps(mWiFiAgent);
 
-        startCaptivePortalApp(mWiFiNetworkAgent);
+        startCaptivePortalApp(mWiFiAgent);
 
         // Report that the captive portal is dismissed, and check that callbacks are fired
-        mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
-        mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
-        validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
-        captivePortalCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        mWiFiAgent.setNetworkValid(false /* privateDnsProbeSent */);
+        mWiFiAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+        validatedCallback.expectAvailableCallbacksValidated(mWiFiAgent);
+        captivePortalCallback.expect(LOST, mWiFiAgent);
 
         mCm.unregisterNetworkCallback(validatedCallback);
         mCm.unregisterNetworkCallback(captivePortalCallback);
@@ -4517,11 +4763,11 @@
                 .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
         mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connectWithCaptivePortal(TEST_REDIRECT_URL, false);
-        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connectWithCaptivePortal(TEST_REDIRECT_URL, false);
+        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
 
-        final Intent signInIntent = startCaptivePortalApp(mWiFiNetworkAgent);
+        final Intent signInIntent = startCaptivePortalApp(mWiFiAgent);
         final CaptivePortal captivePortal = signInIntent
                 .getParcelableExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
 
@@ -4529,14 +4775,14 @@
         waitForIdle();
 
         // Since network will disconnect, ensure no notification of response to NetworkMonitor
-        verify(mWiFiNetworkAgent.mNetworkMonitor, never())
+        verify(mWiFiAgent.mNetworkMonitor, never())
                 .notifyCaptivePortalAppFinished(CaptivePortal.APP_RETURN_UNWANTED);
 
         // Report that the network is disconnected
-        mWiFiNetworkAgent.expectDisconnected();
-        mWiFiNetworkAgent.expectPreventReconnectReceived();
-        verify(mWiFiNetworkAgent.mNetworkMonitor).notifyNetworkDisconnected();
-        captivePortalCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        mWiFiAgent.expectDisconnected();
+        mWiFiAgent.expectPreventReconnectReceived();
+        verify(mWiFiAgent.mNetworkMonitor).notifyNetworkDisconnected();
+        captivePortalCallback.expect(LOST, mWiFiAgent);
 
         mCm.unregisterNetworkCallback(captivePortalCallback);
     }
@@ -4556,12 +4802,12 @@
         setCaptivePortalMode(ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_AVOID);
         // Bring up a network with a captive portal.
         // Expect it to fail to connect and not result in any callbacks.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        String firstRedirectUrl = "http://example.com/firstPath";
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        final String firstRedirectUrl = "http://example.com/firstPath";
 
-        mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl, false /* isStrictMode */);
-        mWiFiNetworkAgent.expectDisconnected();
-        mWiFiNetworkAgent.expectPreventReconnectReceived();
+        mWiFiAgent.connectWithCaptivePortal(firstRedirectUrl, false /* privateDnsProbeSent */);
+        mWiFiAgent.expectDisconnected();
+        mWiFiAgent.expectPreventReconnectReceived();
 
         assertNoCallbacks(captivePortalCallback, validatedCallback);
     }
@@ -4575,28 +4821,30 @@
                 .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
         mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         final String redirectUrl = "http://example.com/firstPath";
 
-        mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl, false /* isStrictMode */);
-        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mWiFiAgent.connectWithCaptivePortal(redirectUrl,
+                false /* privateDnsProbeSent */);
+        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
 
         final CaptivePortalData testData = new CaptivePortalData.Builder()
                 .setUserPortalUrl(Uri.parse(redirectUrl))
                 .setBytesRemaining(12345L)
                 .build();
 
-        mWiFiNetworkAgent.notifyCapportApiDataChanged(testData);
+        mWiFiAgent.notifyCapportApiDataChanged(testData);
 
-        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> testData.equals(lp.getCaptivePortalData()));
+        captivePortalCallback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent,
+                cb -> testData.equals(cb.getLp().getCaptivePortalData()));
 
         final LinkProperties newLps = new LinkProperties();
         newLps.setMtu(1234);
-        mWiFiNetworkAgent.sendLinkProperties(newLps);
+        mWiFiAgent.sendLinkProperties(newLps);
         // CaptivePortalData is not lost and unchanged when LPs are received from the NetworkAgent
-        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> testData.equals(lp.getCaptivePortalData()) && lp.getMtu() == 1234);
+        captivePortalCallback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent,
+                cb -> testData.equals(cb.getLp().getCaptivePortalData())
+                        && cb.getLp().getMtu() == 1234);
     }
 
     private TestNetworkCallback setupNetworkCallbackAndConnectToWifi() throws Exception {
@@ -4609,10 +4857,11 @@
                 .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
         mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
 
-        mWiFiNetworkAgent.connectWithCaptivePortal(TEST_REDIRECT_URL, false /* isStrictMode */);
-        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mWiFiAgent.connectWithCaptivePortal(TEST_REDIRECT_URL,
+                false /* privateDnsProbeSent */);
+        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         return captivePortalCallback;
     }
 
@@ -4687,53 +4936,54 @@
         final CaptivePortalTestData captivePortalTestData = setupCaptivePortalData();
 
         // Baseline capport data
-        mWiFiNetworkAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData);
+        mWiFiAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData);
 
-        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData()));
+        captivePortalCallback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent,
+                cb -> captivePortalTestData.mCapportData.equals(cb.getLp().getCaptivePortalData()));
 
         // Venue URL, T&C URL and friendly name from Network agent with Passpoint source, confirm
         // that API data gets precedence on the bytes remaining.
         final LinkProperties linkProperties = new LinkProperties();
         linkProperties.setCaptivePortalData(captivePortalTestData.mNaPasspointData);
-        mWiFiNetworkAgent.sendLinkProperties(linkProperties);
+        mWiFiAgent.sendLinkProperties(linkProperties);
 
         // Make sure that the capport data is merged
-        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mExpectedMergedPasspointData
-                        .equals(lp.getCaptivePortalData()));
+        captivePortalCallback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent,
+                cb -> captivePortalTestData.mExpectedMergedPasspointData.equals(
+                        cb.getLp().getCaptivePortalData()));
 
         // Now send this information from non-Passpoint source, confirm that Capport data takes
         // precedence
         linkProperties.setCaptivePortalData(captivePortalTestData.mNaOtherData);
-        mWiFiNetworkAgent.sendLinkProperties(linkProperties);
+        mWiFiAgent.sendLinkProperties(linkProperties);
 
         // Make sure that the capport data is merged
-        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mExpectedMergedOtherData
-                        .equals(lp.getCaptivePortalData()));
+        captivePortalCallback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent,
+                cb -> captivePortalTestData.mExpectedMergedOtherData.equals(
+                        cb.getLp().getCaptivePortalData()));
 
         // Create a new LP with no Network agent capport data
         final LinkProperties newLps = new LinkProperties();
         newLps.setMtu(1234);
-        mWiFiNetworkAgent.sendLinkProperties(newLps);
+        mWiFiAgent.sendLinkProperties(newLps);
         // CaptivePortalData is not lost and has the original values when LPs are received from the
         // NetworkAgent
-        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData())
-                        && lp.getMtu() == 1234);
+        captivePortalCallback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent,
+                cb -> captivePortalTestData.mCapportData.equals(cb.getLp().getCaptivePortalData())
+                        && cb.getLp().getMtu() == 1234);
 
         // Now send capport data only from the Network agent
-        mWiFiNetworkAgent.notifyCapportApiDataChanged(null);
-        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> lp.getCaptivePortalData() == null);
+        mWiFiAgent.notifyCapportApiDataChanged(null);
+        captivePortalCallback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent,
+                cb -> cb.getLp().getCaptivePortalData() == null);
 
         newLps.setCaptivePortalData(captivePortalTestData.mNaPasspointData);
-        mWiFiNetworkAgent.sendLinkProperties(newLps);
+        mWiFiAgent.sendLinkProperties(newLps);
 
         // Make sure that only the network agent capport data is available
-        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mNaPasspointData.equals(lp.getCaptivePortalData()));
+        captivePortalCallback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent,
+                cb -> captivePortalTestData.mNaPasspointData.equals(
+                        cb.getLp().getCaptivePortalData()));
     }
 
     @Test
@@ -4745,28 +4995,29 @@
         // on the bytes remaining.
         final LinkProperties linkProperties = new LinkProperties();
         linkProperties.setCaptivePortalData(captivePortalTestData.mNaPasspointData);
-        mWiFiNetworkAgent.sendLinkProperties(linkProperties);
+        mWiFiAgent.sendLinkProperties(linkProperties);
 
         // Make sure that the data is saved correctly
-        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mNaPasspointData.equals(lp.getCaptivePortalData()));
+        captivePortalCallback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent,
+                cb -> captivePortalTestData.mNaPasspointData.equals(
+                        cb.getLp().getCaptivePortalData()));
 
         // Expected merged data: Network agent data is preferred, and values that are not used by
         // it are merged from capport data
-        mWiFiNetworkAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData);
+        mWiFiAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData);
 
         // Make sure that the Capport data is merged correctly
-        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mExpectedMergedPasspointData.equals(
-                        lp.getCaptivePortalData()));
+        captivePortalCallback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent,
+                cb -> captivePortalTestData.mExpectedMergedPasspointData.equals(
+                        cb.getLp().getCaptivePortalData()));
 
         // Now set the naData to null
         linkProperties.setCaptivePortalData(null);
-        mWiFiNetworkAgent.sendLinkProperties(linkProperties);
+        mWiFiAgent.sendLinkProperties(linkProperties);
 
         // Make sure that the Capport data is retained correctly
-        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData()));
+        captivePortalCallback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent,
+                cb -> captivePortalTestData.mCapportData.equals(cb.getLp().getCaptivePortalData()));
     }
 
     @Test
@@ -4779,20 +5030,20 @@
         // on the bytes remaining.
         final LinkProperties linkProperties = new LinkProperties();
         linkProperties.setCaptivePortalData(captivePortalTestData.mNaOtherData);
-        mWiFiNetworkAgent.sendLinkProperties(linkProperties);
+        mWiFiAgent.sendLinkProperties(linkProperties);
 
         // Make sure that the data is saved correctly
-        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mNaOtherData.equals(lp.getCaptivePortalData()));
+        captivePortalCallback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent,
+                cb -> captivePortalTestData.mNaOtherData.equals(cb.getLp().getCaptivePortalData()));
 
         // Expected merged data: Network agent data is preferred, and values that are not used by
         // it are merged from capport data
-        mWiFiNetworkAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData);
+        mWiFiAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData);
 
         // Make sure that the Capport data is merged correctly
-        captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
-                lp -> captivePortalTestData.mExpectedMergedOtherData.equals(
-                        lp.getCaptivePortalData()));
+        captivePortalCallback.expect(LINK_PROPERTIES_CHANGED, mWiFiAgent,
+                cb -> captivePortalTestData.mExpectedMergedOtherData.equals(
+                        cb.getLp().getCaptivePortalData()));
     }
 
     private NetworkRequest.Builder newWifiRequestBuilder() {
@@ -4912,51 +5163,50 @@
         LocalStringNetworkSpecifier nsFoo = new LocalStringNetworkSpecifier("foo");
         LocalStringNetworkSpecifier nsBar = new LocalStringNetworkSpecifier("bar");
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);
-        expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiNetworkAgent, null /* specifier */,
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);
+        expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiAgent, null /* specifier */,
                 cEmpty1, cEmpty2, cEmpty3, cEmpty4);
         assertNoCallbacks(cFoo, cBar);
 
-        mWiFiNetworkAgent.disconnect();
-        expectOnLost(mWiFiNetworkAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4);
+        mWiFiAgent.disconnect();
+        expectOnLost(mWiFiAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.setNetworkSpecifier(nsFoo);
-        mWiFiNetworkAgent.connect(false);
-        expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiNetworkAgent, nsFoo,
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.setNetworkSpecifier(nsFoo);
+        mWiFiAgent.connect(false);
+        expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiAgent, nsFoo,
                 cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo);
         cBar.assertNoCallback();
         assertEquals(nsFoo,
-                mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier());
+                mCm.getNetworkCapabilities(mWiFiAgent.getNetwork()).getNetworkSpecifier());
         assertNoCallbacks(cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo);
 
-        mWiFiNetworkAgent.disconnect();
-        expectOnLost(mWiFiNetworkAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo);
+        mWiFiAgent.disconnect();
+        expectOnLost(mWiFiAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.setNetworkSpecifier(nsBar);
-        mWiFiNetworkAgent.connect(false);
-        expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiNetworkAgent, nsBar,
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.setNetworkSpecifier(nsBar);
+        mWiFiAgent.connect(false);
+        expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiAgent, nsBar,
                 cEmpty1, cEmpty2, cEmpty3, cEmpty4, cBar);
         cFoo.assertNoCallback();
         assertEquals(nsBar,
-                mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier());
+                mCm.getNetworkCapabilities(mWiFiAgent.getNetwork()).getNetworkSpecifier());
 
-        mWiFiNetworkAgent.disconnect();
-        expectOnLost(mWiFiNetworkAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4, cBar);
+        mWiFiAgent.disconnect();
+        expectOnLost(mWiFiAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4, cBar);
         cFoo.assertNoCallback();
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.setNetworkSpecifier(new ConfidentialMatchAllNetworkSpecifier());
-        mWiFiNetworkAgent.connect(false);
-        expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiNetworkAgent, null /* specifier */,
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.setNetworkSpecifier(new ConfidentialMatchAllNetworkSpecifier());
+        mWiFiAgent.connect(false);
+        expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiAgent, null /* specifier */,
                 cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo, cBar);
-        assertNull(
-                mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier());
+        assertNull(mCm.getNetworkCapabilities(mWiFiAgent.getNetwork()).getNetworkSpecifier());
 
-        mWiFiNetworkAgent.disconnect();
-        expectOnLost(mWiFiNetworkAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo, cBar);
+        mWiFiAgent.disconnect();
+        expectOnLost(mWiFiAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo, cBar);
     }
 
     /**
@@ -5035,8 +5285,8 @@
 
     @Test
     public void testNetworkRequestUidSpoofSecurityException() throws Exception {
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);
         NetworkRequest networkRequest = newWifiRequestBuilder().build();
         TestNetworkCallback networkCallback = new TestNetworkCallback();
         doThrow(new SecurityException()).when(mAppOpsManager).checkPackage(anyInt(), anyString());
@@ -5091,35 +5341,35 @@
         cellNetworkCallback.assertNoCallback();
 
         // Bring up cell and expect CALLBACK_AVAILABLE.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        systemDefaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        systemDefaultCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
         assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi and expect CALLBACK_AVAILABLE.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
         cellNetworkCallback.assertNoCallback();
-        defaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
-        systemDefaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        defaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
+        systemDefaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
         assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring down cell. Expect no default network callback, since it wasn't the default.
-        mCellNetworkAgent.disconnect();
-        cellNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        mCellAgent.disconnect();
+        cellNetworkCallback.expect(LOST, mCellAgent);
         defaultNetworkCallback.assertNoCallback();
         systemDefaultCallback.assertNoCallback();
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
         assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         // Bring up cell. Expect no default network callback, since it won't be the default.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
         defaultNetworkCallback.assertNoCallback();
         systemDefaultCallback.assertNoCallback();
         assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
@@ -5127,16 +5377,16 @@
 
         // Bring down wifi. Expect the default network callback to notified of LOST wifi
         // followed by AVAILABLE cell.
-        mWiFiNetworkAgent.disconnect();
+        mWiFiAgent.disconnect();
         cellNetworkCallback.assertNoCallback();
-        defaultNetworkCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        defaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
-        systemDefaultCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        systemDefaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
-        mCellNetworkAgent.disconnect();
-        cellNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
-        defaultNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
-        systemDefaultCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        defaultNetworkCallback.expect(LOST, mWiFiAgent);
+        defaultNetworkCallback.expectAvailableCallbacksValidated(mCellAgent);
+        systemDefaultCallback.expect(LOST, mWiFiAgent);
+        systemDefaultCallback.expectAvailableCallbacksValidated(mCellAgent);
+        mCellAgent.disconnect();
+        cellNetworkCallback.expect(LOST, mCellAgent);
+        defaultNetworkCallback.expect(LOST, mCellAgent);
+        systemDefaultCallback.expect(LOST, mCellAgent);
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
 
@@ -5148,7 +5398,7 @@
         assertEquals(null, systemDefaultCallback.getLastAvailableNetwork());
 
         mMockVpn.disconnect();
-        defaultNetworkCallback.expect(CallbackEntry.LOST, mMockVpn);
+        defaultNetworkCallback.expect(LOST, mMockVpn);
         systemDefaultCallback.assertNoCallback();
         waitForIdle();
         assertEquals(null, mCm.getActiveNetwork());
@@ -5163,29 +5413,28 @@
         mCm.requestNetwork(cellRequest, cellNetworkCallback);
 
         // Bring up the mobile network.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
 
         // We should get onAvailable(), onCapabilitiesChanged(), and
         // onLinkPropertiesChanged() in rapid succession. Additionally, we
         // should get onCapabilitiesChanged() when the mobile network validates.
-        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
         cellNetworkCallback.assertNoCallback();
 
         // Update LinkProperties.
         final LinkProperties lp = new LinkProperties();
         lp.setInterfaceName("foonet_data0");
-        mCellNetworkAgent.sendLinkProperties(lp);
+        mCellAgent.sendLinkProperties(lp);
         // We should get onLinkPropertiesChanged().
-        cellNetworkCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED,
-                mCellNetworkAgent);
+        cellNetworkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
         cellNetworkCallback.assertNoCallback();
 
         // Suspend the network.
-        mCellNetworkAgent.suspend();
-        cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_SUSPENDED,
-                mCellNetworkAgent);
-        cellNetworkCallback.expect(CallbackEntry.SUSPENDED, mCellNetworkAgent);
+        mCellAgent.suspend();
+        cellNetworkCallback.expectCaps(mCellAgent,
+                c -> !c.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+        cellNetworkCallback.expect(SUSPENDED, mCellAgent);
         cellNetworkCallback.assertNoCallback();
         assertEquals(NetworkInfo.State.SUSPENDED, mCm.getActiveNetworkInfo().getState());
 
@@ -5194,21 +5443,21 @@
         mCm.registerDefaultNetworkCallback(dfltNetworkCallback);
         // We should get onAvailable(), onCapabilitiesChanged(), onLinkPropertiesChanged(),
         // as well as onNetworkSuspended() in rapid succession.
-        dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent, true);
+        dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellAgent, true);
         dfltNetworkCallback.assertNoCallback();
         mCm.unregisterNetworkCallback(dfltNetworkCallback);
 
-        mCellNetworkAgent.resume();
-        cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_SUSPENDED,
-                mCellNetworkAgent);
-        cellNetworkCallback.expect(CallbackEntry.RESUMED, mCellNetworkAgent);
+        mCellAgent.resume();
+        cellNetworkCallback.expectCaps(mCellAgent,
+                c -> c.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+        cellNetworkCallback.expect(RESUMED, mCellAgent);
         cellNetworkCallback.assertNoCallback();
         assertEquals(NetworkInfo.State.CONNECTED, mCm.getActiveNetworkInfo().getState());
 
         dfltNetworkCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(dfltNetworkCallback);
         // This time onNetworkSuspended should not be called.
-        dfltNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        dfltNetworkCallback.expectAvailableCallbacksValidated(mCellAgent);
         dfltNetworkCallback.assertNoCallback();
 
         mCm.unregisterNetworkCallback(dfltNetworkCallback);
@@ -5217,8 +5466,8 @@
 
     @Test
     public void testRegisterPrivilegedDefaultCallbacksRequirePermissions() throws Exception {
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(false /* validated */);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(false /* validated */);
         mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_DENIED);
 
         final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
@@ -5233,16 +5482,23 @@
         mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_GRANTED);
         mCm.registerSystemDefaultNetworkCallback(callback, handler);
         mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_DENIED);
-        callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mCellAgent);
         mCm.unregisterNetworkCallback(callback);
 
         mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
         mCm.registerSystemDefaultNetworkCallback(callback, handler);
-        callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mCellAgent);
         mCm.unregisterNetworkCallback(callback);
 
+        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED);
+        mServiceContext.setPermission(NETWORK_SETUP_WIZARD, PERMISSION_GRANTED);
+        mCm.registerSystemDefaultNetworkCallback(callback, handler);
+        callback.expectAvailableCallbacksUnvalidated(mCellAgent);
+        mCm.unregisterNetworkCallback(callback);
+
+        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
         mCm.registerDefaultNetworkCallbackForUid(APP1_UID, callback, handler);
-        callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mCellAgent);
         mCm.unregisterNetworkCallback(callback);
     }
 
@@ -5273,15 +5529,15 @@
         mCm.registerNetworkCallback(includeOtherUidsRequest, includeOtherUidsCallback);
 
         // Both callbacks see a network with no specifier that applies to their UID.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false /* validated */);
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        otherUidCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        includeOtherUidsCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        mWiFiNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        otherUidCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        includeOtherUidsCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false /* validated */);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        otherUidCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        includeOtherUidsCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        mWiFiAgent.disconnect();
+        callback.expect(LOST, mWiFiAgent);
+        otherUidCallback.expect(LOST, mWiFiAgent);
+        includeOtherUidsCallback.expect(LOST, mWiFiAgent);
 
         // Only the includeOtherUidsCallback sees a VPN that does not apply to its UID.
         final UidRange range = UidRange.createForUser(UserHandle.of(RESTRICTED_USER));
@@ -5292,7 +5548,7 @@
         otherUidCallback.assertNoCallback();
 
         mMockVpn.disconnect();
-        includeOtherUidsCallback.expect(CallbackEntry.LOST, mMockVpn);
+        includeOtherUidsCallback.expect(LOST, mMockVpn);
         callback.assertNoCallback();
         otherUidCallback.assertNoCallback();
     }
@@ -5357,10 +5613,10 @@
         final NetworkCapabilities ncTemplate = new NetworkCapabilities()
                 .addTransportType(TRANSPORT_WIFI)
                 .setNetworkSpecifier(specifier);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, emptyLp, ncTemplate);
-        mWiFiNetworkAgent.connect(false /* validated */);
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        otherUidCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, emptyLp, ncTemplate);
+        mWiFiAgent.connect(false /* validated */);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        otherUidCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         includeOtherUidsCallback.assertNoCallback();
     }
 
@@ -5415,63 +5671,63 @@
         mCm.registerNetworkCallback(request, callback);
         mCm.registerNetworkCallback(fgRequest, fgCallback);
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        fgCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        assertTrue(isForegroundNetwork(mCellNetworkAgent));
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        callback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        fgCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        assertTrue(isForegroundNetwork(mCellAgent));
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
 
         // When wifi connects, cell lingers.
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectLosing(mCellNetworkAgent);
-        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        fgCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        fgCallback.expectLosing(mCellNetworkAgent);
-        fgCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        assertTrue(isForegroundNetwork(mCellNetworkAgent));
-        assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        callback.expectLosing(mCellAgent);
+        callback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
+        fgCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        fgCallback.expectLosing(mCellAgent);
+        fgCallback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
+        assertTrue(isForegroundNetwork(mCellAgent));
+        assertTrue(isForegroundNetwork(mWiFiAgent));
 
         // When lingering is complete, cell is still there but is now in the background.
         waitForIdle();
         int timeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
-        fgCallback.expect(CallbackEntry.LOST, mCellNetworkAgent, timeoutMs);
+        fgCallback.expect(LOST, mCellAgent, timeoutMs);
         // Expect a network capabilities update sans FOREGROUND.
-        callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
-        assertFalse(isForegroundNetwork(mCellNetworkAgent));
-        assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
+        callback.expectCaps(mCellAgent, c -> !c.hasCapability(NET_CAPABILITY_FOREGROUND));
+        assertFalse(isForegroundNetwork(mCellAgent));
+        assertTrue(isForegroundNetwork(mWiFiAgent));
 
         // File a cell request and check that cell comes into the foreground.
         final NetworkRequest cellRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_CELLULAR).build();
         final TestNetworkCallback cellCallback = new TestNetworkCallback();
         mCm.requestNetwork(cellRequest, cellCallback);
-        cellCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
-        fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        cellCallback.expectAvailableCallbacksValidated(mCellAgent);
+        fgCallback.expectAvailableCallbacksValidated(mCellAgent);
         // Expect a network capabilities update with FOREGROUND, because the most recent
         // request causes its state to change.
-        cellCallback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
-        callback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
-        assertTrue(isForegroundNetwork(mCellNetworkAgent));
-        assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
+        cellCallback.expectCaps(mCellAgent, c -> c.hasCapability(NET_CAPABILITY_FOREGROUND));
+        callback.expectCaps(mCellAgent, c -> c.hasCapability(NET_CAPABILITY_FOREGROUND));
+        assertTrue(isForegroundNetwork(mCellAgent));
+        assertTrue(isForegroundNetwork(mWiFiAgent));
 
         // Release the request. The network immediately goes into the background, since it was not
         // lingering.
         mCm.unregisterNetworkCallback(cellCallback);
-        fgCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        fgCallback.expect(LOST, mCellAgent);
         // Expect a network capabilities update sans FOREGROUND.
-        callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
-        assertFalse(isForegroundNetwork(mCellNetworkAgent));
-        assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
+        callback.expectCaps(mCellAgent, c -> !c.hasCapability(NET_CAPABILITY_FOREGROUND));
+        assertFalse(isForegroundNetwork(mCellAgent));
+        assertTrue(isForegroundNetwork(mWiFiAgent));
 
         // Disconnect wifi and check that cell is foreground again.
-        mWiFiNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        fgCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
-        assertTrue(isForegroundNetwork(mCellNetworkAgent));
+        mWiFiAgent.disconnect();
+        callback.expect(LOST, mWiFiAgent);
+        fgCallback.expect(LOST, mWiFiAgent);
+        fgCallback.expectAvailableCallbacksValidated(mCellAgent);
+        assertTrue(isForegroundNetwork(mCellAgent));
 
         mCm.unregisterNetworkCallback(callback);
         mCm.unregisterNetworkCallback(fgCallback);
@@ -5512,11 +5768,11 @@
             }
         });
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         // Don't request that the network validate, because otherwise connect() will block until
         // the network gets NET_CAPABILITY_VALIDATED, after all the callbacks below have fired,
         // and we won't actually measure anything.
-        mCellNetworkAgent.connect(false);
+        mCellAgent.connect(false);
 
         long onAvailableDispatchingDuration = durationOf(() -> {
             await(availableLatch, 10 * CONNECT_TIME_LIMIT_MS);
@@ -5529,9 +5785,9 @@
                 onAvailableDispatchingDuration <= CONNECT_TIME_LIMIT_MS);
 
         // Give wifi a high enough score that we'll linger cell when wifi comes up.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.adjustScore(40);
-        mWiFiNetworkAgent.connect(false);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.adjustScore(40);
+        mWiFiAgent.connect(false);
 
         long onLostDispatchingDuration = durationOf(() -> {
             await(losingLatch, 10 * SWITCH_TIME_LIMIT_MS);
@@ -5577,9 +5833,9 @@
             assertTrue(testFactory.getMyStartRequested());
 
             // Bring up wifi. The factory stops looking for a network.
-            mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+            mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
             // Score 60 - 40 penalty for not validated yet, then 60 when it validates
-            mWiFiNetworkAgent.connect(true);
+            mWiFiAgent.connect(true);
             // The network connects with a low score, so the offer can still beat it and
             // nothing happens. Then the network validates, and the offer with its filter score
             // of 40 can no longer beat it and the request is removed.
@@ -5598,9 +5854,9 @@
 
             // Bring up cell data and check that the factory stops looking.
             assertLength(1, mCm.getAllNetworks());
-            mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-            mCellNetworkAgent.connect(false);
-            cellNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent, false, false, false,
+            mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+            mCellAgent.connect(false);
+            cellNetworkCallback.expectAvailableCallbacks(mCellAgent, false, false, false,
                     TEST_CALLBACK_TIMEOUT_MS);
             // When cell connects, it will satisfy the "mobile always on request" right away
             // by virtue of being the only network that can satisfy the request. However, its
@@ -5610,11 +5866,12 @@
             // Next, cell validates. This gives it a score of 50 and the test factory can't
             // hope to beat that according to its filters. It will see the message that its
             // offer is now unnecessary.
-            mCellNetworkAgent.setNetworkValid(true);
+            mCellAgent.setNetworkValid(true);
             // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is
             // validated – see testPartialConnectivity.
-            mCm.reportNetworkConnectivity(mCellNetworkAgent.getNetwork(), true);
-            cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mCellNetworkAgent);
+            mCm.reportNetworkConnectivity(mCellAgent.getNetwork(), true);
+            cellNetworkCallback.expectCaps(mCellAgent,
+                    c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
             testFactory.expectRequestRemove();
             testFactory.assertRequestCountEquals(0);
             // Accordingly, the factory shouldn't be started.
@@ -5627,8 +5884,8 @@
 
             // Cell disconnects. There is still the "mobile data always on" request outstanding,
             // and the test factory should see it now that it isn't hopelessly outscored.
-            mCellNetworkAgent.disconnect();
-            cellNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+            mCellAgent.disconnect();
+            cellNetworkCallback.expect(LOST, mCellAgent);
             // Wait for the network to be removed from internal structures before
             // calling synchronous getter
             waitForIdle();
@@ -5639,9 +5896,9 @@
             // Reconnect cell validated, see the request disappear again. Then withdraw the
             // mobile always on request. This will tear down cell, and there shouldn't be a
             // blip where the test factory briefly sees the request or anything.
-            mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-            mCellNetworkAgent.connect(true);
-            cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+            mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+            mCellAgent.connect(true);
+            cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
             waitForIdle();
             assertLength(2, mCm.getAllNetworks());
             testFactory.expectRequestRemove();
@@ -5651,7 +5908,7 @@
             testFactory.assertRequestCountEquals(0);
             assertFalse(testFactory.getMyStartRequested());
             // ...  and cell data to be torn down immediately since it is no longer nascent.
-            cellNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+            cellNetworkCallback.expect(LOST, mCellAgent);
             waitForIdle();
             assertLength(1, mCm.getAllNetworks());
             testFactory.terminate();
@@ -5732,7 +5989,7 @@
         doReturn(0).when(mResources).getInteger(R.integer.config_activelyPreferBadWifi);
         mPolicyTracker.reevaluate();
         waitForIdle();
-        if (SdkLevel.isAtLeastU()) {
+        if (mDeps.isAtLeastU()) {
             // U+ ignore the setting and always actively prefers bad wifi
             assertTrue(mService.mNetworkRanker.getConfiguration().activelyPreferBadWifi());
         } else {
@@ -5796,39 +6053,40 @@
         mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
 
         // Cell connects and validates.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
                 new LinkProperties(), null /* ncTemplate */, cellProvider);
-        mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent.connect(true);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
         cellCallback.assertNoCallback();
         wifiCallback.assertNoCallback();
 
         // Bring up wifi. At first it's invalidated, so cell is still needed.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI,
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI,
                 new LinkProperties(), null /* ncTemplate */, wifiProvider);
-        mWiFiNetworkAgent.connect(false);
-        wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mWiFiAgent.connect(false);
+        wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         cellCallback.assertNoCallback();
         wifiCallback.assertNoCallback();
 
         // Wifi validates. Cell is no longer needed, because it's outscored.
-        mWiFiNetworkAgent.setNetworkValid(true /* isStrictMode */);
+        mWiFiAgent.setNetworkValid(true /* privateDnsProbeSent */);
         // Have CS reconsider the network (see testPartialConnectivity)
-        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
-        wifiNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        mCm.reportNetworkConnectivity(mWiFiAgent.getNetwork(), true);
+        wifiNetworkCallback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
         cellCallback.expectOnNetworkUnneeded(defaultCaps);
         wifiCallback.assertNoCallback();
 
         // Wifi is no longer validated. Cell is needed again.
-        mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */);
-        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
-        wifiNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        mWiFiAgent.setNetworkInvalid(true /* invalidBecauseOfPrivateDns */);
+        mCm.reportNetworkConnectivity(mWiFiAgent.getNetwork(), false);
+        wifiNetworkCallback.expectCaps(mWiFiAgent,
+                c -> !c.hasCapability(NET_CAPABILITY_VALIDATED));
         cellCallback.expectOnNetworkNeeded(defaultCaps);
         wifiCallback.assertNoCallback();
 
         // Disconnect wifi and pretend the carrier restricts moving away from bad wifi.
-        mWiFiNetworkAgent.disconnect();
-        wifiNetworkCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        mWiFiAgent.disconnect();
+        wifiNetworkCallback.expect(LOST, mWiFiAgent);
         // This has getAvoidBadWifi return false. This test doesn't change the value of the
         // associated setting.
         doReturn(0).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
@@ -5836,31 +6094,36 @@
         waitForIdle();
 
         // Connect wifi again, cell is needed until wifi validates.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI,
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI,
                 new LinkProperties(), null /* ncTemplate */, wifiProvider);
-        mWiFiNetworkAgent.connect(false);
-        wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mWiFiAgent.connect(false);
+        wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         cellCallback.assertNoCallback();
         wifiCallback.assertNoCallback();
-        mWiFiNetworkAgent.setNetworkValid(true /* isStrictMode */);
-        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
-        wifiNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        mWiFiAgent.setNetworkValid(true /* privateDnsProbeSent */);
+        mCm.reportNetworkConnectivity(mWiFiAgent.getNetwork(), true);
+        wifiNetworkCallback.expectCaps(mWiFiAgent,
+                c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
         cellCallback.expectOnNetworkUnneeded(defaultCaps);
         wifiCallback.assertNoCallback();
 
         // Wifi loses validation. Because the device doesn't avoid bad wifis, cell is
         // not needed.
-        mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */);
-        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
-        wifiNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        mWiFiAgent.setNetworkInvalid(true /* invalidBecauseOfPrivateDns */);
+        mCm.reportNetworkConnectivity(mWiFiAgent.getNetwork(), false);
+        wifiNetworkCallback.expectCaps(mWiFiAgent,
+                c -> !c.hasCapability(NET_CAPABILITY_VALIDATED));
         cellCallback.assertNoCallback();
         wifiCallback.assertNoCallback();
     }
 
-    public void doTestPreferBadWifi(final boolean preferBadWifi) throws Exception {
+    public void doTestPreferBadWifi(final boolean avoidBadWifi,
+            final boolean preferBadWifi,
+            @NonNull Predicate<Long> checkUnvalidationTimeout) throws Exception {
         // Pretend we're on a carrier that restricts switching away from bad wifi, and
         // depending on the parameter one that may indeed prefer bad wifi.
-        doReturn(0).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
+        doReturn(avoidBadWifi ? 1 : 0).when(mResources)
+                .getInteger(R.integer.config_networkAvoidBadWifi);
         doReturn(preferBadWifi ? 1 : 0).when(mResources)
                 .getInteger(R.integer.config_activelyPreferBadWifi);
         mPolicyTracker.reevaluate();
@@ -5870,37 +6133,55 @@
                 .clearCapabilities()
                 .addTransportType(TRANSPORT_WIFI)
                 .build();
-        final TestableNetworkCallback wifiCallback = new TestableNetworkCallback();
+        final TestNetworkCallback wifiCallback = new TestNetworkCallback();
         mCm.registerNetworkCallback(wifiRequest, wifiCallback);
 
         // Bring up validated cell and unvalidated wifi.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);
-        wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);
+        wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
 
-        if (preferBadWifi) {
-            expectUnvalidationCheckWillNotify(mWiFiNetworkAgent, NotificationType.LOST_INTERNET);
-            mDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mDeps.mScheduledEvaluationTimeouts.poll(TIMEOUT_MS, t -> checkUnvalidationTimeout.test(t));
+
+        if (!avoidBadWifi && preferBadWifi) {
+            expectUnvalidationCheckWillNotify(mWiFiAgent, NotificationType.LOST_INTERNET);
+            mDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         } else {
-            expectUnvalidationCheckWillNotNotify(mWiFiNetworkAgent);
+            expectUnvalidationCheckWillNotNotify(mWiFiAgent);
             mDefaultNetworkCallback.assertNoCallback();
         }
     }
 
     @Test
-    public void testPreferBadWifi_doNotPrefer() throws Exception {
+    public void testPreferBadWifi_doNotAvoid_doNotPrefer() throws Exception {
         // Starting with U this mode is no longer supported and can't actually be tested
-        assumeFalse(SdkLevel.isAtLeastU());
-        doTestPreferBadWifi(false /* preferBadWifi */);
+        assumeFalse(mDeps.isAtLeastU());
+        doTestPreferBadWifi(false /* avoidBadWifi */, false /* preferBadWifi */,
+                timeout -> timeout < 14_000);
     }
 
     @Test
-    public void testPreferBadWifi_doPrefer() throws Exception {
-        doTestPreferBadWifi(true /* preferBadWifi */);
+    public void testPreferBadWifi_doNotAvoid_doPrefer() throws Exception {
+        doTestPreferBadWifi(false /* avoidBadWifi */, true /* preferBadWifi */,
+                timeout -> timeout > 14_000);
+    }
+
+    @Test
+    public void testPreferBadWifi_doAvoid_doNotPrefer() throws Exception {
+        // If avoidBadWifi=true, then preferBadWifi should be irrelevant. Test anyway.
+        doTestPreferBadWifi(true /* avoidBadWifi */, false /* preferBadWifi */,
+                timeout -> timeout < 14_000);
+    }
+
+    @Test
+    public void testPreferBadWifi_doAvoid_doPrefer() throws Exception {
+        // If avoidBadWifi=true, then preferBadWifi should be irrelevant. Test anyway.
+        doTestPreferBadWifi(true /* avoidBadWifi */, true /* preferBadWifi */,
+                timeout -> timeout < 14_000);
     }
 
     @Test
@@ -5931,25 +6212,25 @@
         mPolicyTracker.reevaluate();
 
         // Bring up validated cell.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        Network cellNetwork = mCellNetworkAgent.getNetwork();
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        defaultCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        Network cellNetwork = mCellAgent.getNetwork();
 
         // Bring up validated wifi.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
-        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
-        validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
-        Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
+        validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
+        Network wifiNetwork = mWiFiAgent.getNetwork();
 
         // Fail validation on wifi.
-        mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */);
+        mWiFiAgent.setNetworkInvalid(false /* invalidBecauseOfPrivateDns */);
         mCm.reportNetworkConnectivity(wifiNetwork, false);
-        defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        validatedWifiCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        expectNotification(mWiFiNetworkAgent, NotificationType.LOST_INTERNET);
+        defaultCallback.expectCaps(mWiFiAgent, c -> !c.hasCapability(NET_CAPABILITY_VALIDATED));
+        validatedWifiCallback.expect(LOST, mWiFiAgent);
+        expectNotification(mWiFiAgent, NotificationType.LOST_INTERNET);
 
         // Because avoid bad wifi is off, we don't switch to cellular.
         defaultCallback.assertNoCallback();
@@ -5963,24 +6244,24 @@
         // that we switch back to cell.
         doReturn(1).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
         mPolicyTracker.reevaluate();
-        defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mCellAgent);
         assertEquals(mCm.getActiveNetwork(), cellNetwork);
-        expectClearNotification(mWiFiNetworkAgent, NotificationType.LOST_INTERNET);
+        expectClearNotification(mWiFiAgent, NotificationType.LOST_INTERNET);
 
         // Switch back to a restrictive carrier.
         doReturn(0).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
         mPolicyTracker.reevaluate();
-        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         assertEquals(mCm.getActiveNetwork(), wifiNetwork);
         // A notification was already shown for this very network.
-        expectNoNotification(mWiFiNetworkAgent);
+        expectNoNotification(mWiFiAgent);
 
         // Simulate the user selecting "switch" on the dialog, and check that we switch to cell.
         // In principle this is a little bit unrealistic because the switch to a less restrictive
         // carrier above should have remove the notification but this doesn't matter for the
         // purposes of this test.
         mCm.setAvoidUnvalidated(wifiNetwork);
-        defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mCellAgent);
         assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
                 NET_CAPABILITY_VALIDATED));
         assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
@@ -5988,54 +6269,54 @@
         assertEquals(mCm.getActiveNetwork(), cellNetwork);
 
         // Disconnect and reconnect wifi to clear the one-time switch above.
-        mWiFiNetworkAgent.disconnect();
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
-        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
-        validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
-        wifiNetwork = mWiFiNetworkAgent.getNetwork();
+        mWiFiAgent.disconnect();
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
+        validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
+        wifiNetwork = mWiFiAgent.getNetwork();
 
         // Fail validation on wifi and expect the dialog to appear.
-        mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */);
+        mWiFiAgent.setNetworkInvalid(false /* invalidBecauseOfPrivateDns */);
         mCm.reportNetworkConnectivity(wifiNetwork, false);
-        defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        validatedWifiCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        expectNotification(mWiFiNetworkAgent, NotificationType.LOST_INTERNET);
+        defaultCallback.expectCaps(mWiFiAgent, c -> !c.hasCapability(NET_CAPABILITY_VALIDATED));
+        validatedWifiCallback.expect(LOST, mWiFiAgent);
+        expectNotification(mWiFiAgent, NotificationType.LOST_INTERNET);
 
         // Simulate the user selecting "switch" and checking the don't ask again checkbox.
         Settings.Global.putInt(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, 1);
         mPolicyTracker.reevaluate();
 
         // We now switch to cell.
-        defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mCellAgent);
         assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
                 NET_CAPABILITY_VALIDATED));
         assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
                 NET_CAPABILITY_VALIDATED));
         assertEquals(mCm.getActiveNetwork(), cellNetwork);
-        expectClearNotification(mWiFiNetworkAgent, NotificationType.LOST_INTERNET);
+        expectClearNotification(mWiFiAgent, NotificationType.LOST_INTERNET);
 
         // Simulate the user turning the cellular fallback setting off and then on.
         // We switch to wifi and then to cell.
         Settings.Global.putString(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, null);
         mPolicyTracker.reevaluate();
-        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         assertEquals(mCm.getActiveNetwork(), wifiNetwork);
         // Notification is cleared again because CS doesn't particularly remember that it has
         // cleared it before, and if it hasn't cleared it before then it should do so now.
-        expectClearNotification(mWiFiNetworkAgent, NotificationType.LOST_INTERNET);
+        expectClearNotification(mWiFiAgent, NotificationType.LOST_INTERNET);
         Settings.Global.putInt(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, 1);
         mPolicyTracker.reevaluate();
-        defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacksValidated(mCellAgent);
         assertEquals(mCm.getActiveNetwork(), cellNetwork);
 
         // If cell goes down, we switch to wifi.
-        mCellNetworkAgent.disconnect();
-        defaultCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
-        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mCellAgent.disconnect();
+        defaultCallback.expect(LOST, mCellAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         validatedWifiCallback.assertNoCallback();
         // Notification is cleared yet again because the device switched to wifi.
-        expectClearNotification(mWiFiNetworkAgent, NotificationType.LOST_INTERNET);
+        expectClearNotification(mWiFiAgent, NotificationType.LOST_INTERNET);
 
         mCm.unregisterNetworkCallback(cellNetworkCallback);
         mCm.unregisterNetworkCallback(validatedWifiCallback);
@@ -6072,9 +6353,9 @@
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
         mCm.requestNetwork(nr, networkCallback, TEST_REQUEST_TIMEOUT_MS);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);
-        networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, false,
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);
+        networkCallback.expectAvailableCallbacks(mWiFiAgent, false, false, false,
                 TEST_CALLBACK_TIMEOUT_MS);
 
         // pass timeout and validate that UNAVAILABLE is not called
@@ -6092,12 +6373,12 @@
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
         mCm.requestNetwork(nr, networkCallback, TEST_REQUEST_TIMEOUT_MS);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);
-        networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, false,
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);
+        networkCallback.expectAvailableCallbacks(mWiFiAgent, false, false, false,
                 TEST_CALLBACK_TIMEOUT_MS);
-        mWiFiNetworkAgent.disconnect();
-        networkCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        mWiFiAgent.disconnect();
+        networkCallback.expect(LOST, mWiFiAgent);
 
         // Validate that UNAVAILABLE is not called
         networkCallback.assertNoCallback();
@@ -6117,11 +6398,11 @@
         mCm.requestNetwork(nr, networkCallback, timeoutMs);
 
         // pass timeout and validate that UNAVAILABLE is called
-        networkCallback.expect(CallbackEntry.UNAVAILABLE);
+        networkCallback.expect(UNAVAILABLE);
 
         // create a network satisfying request - validate that request not triggered
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);
         networkCallback.assertNoCallback();
     }
 
@@ -6143,8 +6424,8 @@
         networkCallback.assertNoCallback();
 
         // create a network satisfying request - validate that request not triggered
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);
         networkCallback.assertNoCallback();
     }
 
@@ -6201,7 +6482,7 @@
                 // onUnavailable!
                 testFactory.triggerUnfulfillable(newRequest);
 
-                networkCallback.expect(CallbackEntry.UNAVAILABLE);
+                networkCallback.expect(UNAVAILABLE);
 
                 // Declaring a request unfulfillable releases it automatically.
                 testFactory.expectRequestRemove();
@@ -6219,6 +6500,142 @@
         }
     }
 
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @DisableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+    public void testSelfCertifiedCapabilitiesDisabled()
+            throws Exception {
+        mDeps.enableCompatChangeCheck();
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                .build();
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        mCm.requestNetwork(networkRequest, cb);
+        mCm.unregisterNetworkCallback(cb);
+    }
+
+    /** Set the networkSliceResourceId to 0 will result in NameNotFoundException be thrown. */
+    private void setupMockForNetworkCapabilitiesResources(int networkSliceResourceId)
+            throws PackageManager.NameNotFoundException {
+        if (networkSliceResourceId == 0) {
+            doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager).getProperty(
+                    ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+                    mContext.getPackageName());
+        } else {
+            final PackageManager.Property property = new PackageManager.Property(
+                    ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+                    networkSliceResourceId,
+                    true /* isResource */,
+                    mContext.getPackageName(),
+                    "dummyClass"
+            );
+            doReturn(property).when(mPackageManager).getProperty(
+                    ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+                    mContext.getPackageName());
+            doReturn(mContext.getResources()).when(mPackageManager).getResourcesForApplication(
+                    mContext.getPackageName());
+        }
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+    public void requestNetwork_withoutPrioritizeBandwidthDeclaration_shouldThrowException()
+            throws Exception {
+        mDeps.enableCompatChangeCheck();
+        setupMockForNetworkCapabilitiesResources(
+                com.android.frameworks.tests.net.R.xml.self_certified_capabilities_latency);
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                .build();
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        final Exception e = assertThrows(SecurityException.class,
+                () -> mCm.requestNetwork(networkRequest, cb));
+        assertThat(e.getMessage(),
+                containsString(ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_BANDWIDTH));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+    public void requestNetwork_withoutPrioritizeLatencyDeclaration_shouldThrowException()
+            throws Exception {
+        mDeps.enableCompatChangeCheck();
+        setupMockForNetworkCapabilitiesResources(
+                com.android.frameworks.tests.net.R.xml.self_certified_capabilities_bandwidth);
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                .build();
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        final Exception e = assertThrows(SecurityException.class,
+                () -> mCm.requestNetwork(networkRequest, cb));
+        assertThat(e.getMessage(),
+                containsString(ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_LATENCY));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+    public void requestNetwork_withoutNetworkSliceProperty_shouldThrowException() throws Exception {
+        mDeps.enableCompatChangeCheck();
+        setupMockForNetworkCapabilitiesResources(0 /* networkSliceResourceId */);
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                .build();
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        final Exception e = assertThrows(SecurityException.class,
+                () -> mCm.requestNetwork(networkRequest, cb));
+        assertThat(e.getMessage(),
+                containsString(ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+    public void requestNetwork_withNetworkSliceDeclaration_shouldSucceed() throws Exception {
+        mDeps.enableCompatChangeCheck();
+        setupMockForNetworkCapabilitiesResources(
+                com.android.frameworks.tests.net.R.xml.self_certified_capabilities_both);
+
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                .build();
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        mCm.requestNetwork(networkRequest, cb);
+        mCm.unregisterNetworkCallback(cb);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+    public void requestNetwork_withNetworkSliceDeclaration_shouldUseCache() throws Exception {
+        mDeps.enableCompatChangeCheck();
+        setupMockForNetworkCapabilitiesResources(
+                com.android.frameworks.tests.net.R.xml.self_certified_capabilities_both);
+
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                .build();
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        mCm.requestNetwork(networkRequest, cb);
+        mCm.unregisterNetworkCallback(cb);
+
+        // Second call should use caches
+        mCm.requestNetwork(networkRequest, cb);
+        mCm.unregisterNetworkCallback(cb);
+
+        // PackageManager's API only called once because the second call is using cache.
+        verify(mPackageManager, times(1)).getProperty(
+                ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+                mContext.getPackageName());
+        verify(mPackageManager, times(1)).getResourcesForApplication(
+                mContext.getPackageName());
+    }
+
     /**
      * Validate the service throws if request with CBS but without carrier privilege.
      */
@@ -6382,18 +6799,18 @@
 
     private Network connectKeepaliveNetwork(LinkProperties lp) throws Exception {
         // Ensure the network is disconnected before anything else occurs
-        if (mWiFiNetworkAgent != null) {
-            assertNull(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()));
+        if (mWiFiAgent != null) {
+            assertNull(mCm.getNetworkCapabilities(mWiFiAgent.getNetwork()));
         }
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent.connect(true);
         b.expectBroadcast();
         verifyActiveNetwork(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.sendLinkProperties(lp);
+        mWiFiAgent.sendLinkProperties(lp);
         waitForIdle();
-        return mWiFiNetworkAgent.getNetwork();
+        return mWiFiAgent.getNetwork();
     }
 
     @Test
@@ -6450,10 +6867,10 @@
         callback.expectError(PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
 
         // Check that a started keepalive can be stopped.
-        mWiFiNetworkAgent.setStartKeepaliveEvent(PacketKeepalive.SUCCESS);
+        mWiFiAgent.setStartKeepaliveEvent(PacketKeepalive.SUCCESS);
         ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4);
         callback.expectStarted();
-        mWiFiNetworkAgent.setStopKeepaliveEvent(PacketKeepalive.SUCCESS);
+        mWiFiAgent.setStopKeepaliveEvent(PacketKeepalive.SUCCESS);
         ka.stop();
         callback.expectStopped();
 
@@ -6463,15 +6880,15 @@
         callback.expectStarted();
         bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25));
         bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25));
-        mWiFiNetworkAgent.sendLinkProperties(bogusLp);
+        mWiFiAgent.sendLinkProperties(bogusLp);
         callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS);
-        mWiFiNetworkAgent.sendLinkProperties(lp);
+        mWiFiAgent.sendLinkProperties(lp);
 
         // Check that a started keepalive is stopped correctly when the network disconnects.
         ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4);
         callback.expectStarted();
-        mWiFiNetworkAgent.disconnect();
-        mWiFiNetworkAgent.expectDisconnected();
+        mWiFiAgent.disconnect();
+        mWiFiAgent.expectDisconnected();
         callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK);
 
         // ... and that stopping it after that has no adverse effects.
@@ -6482,15 +6899,15 @@
 
         // Reconnect.
         myNet = connectKeepaliveNetwork(lp);
-        mWiFiNetworkAgent.setStartKeepaliveEvent(PacketKeepalive.SUCCESS);
+        mWiFiAgent.setStartKeepaliveEvent(PacketKeepalive.SUCCESS);
 
         // Check that keepalive slots start from 1 and increment. The first one gets slot 1.
-        mWiFiNetworkAgent.setExpectedKeepaliveSlot(1);
+        mWiFiAgent.setExpectedKeepaliveSlot(1);
         ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4);
         callback.expectStarted();
 
         // The second one gets slot 2.
-        mWiFiNetworkAgent.setExpectedKeepaliveSlot(2);
+        mWiFiAgent.setExpectedKeepaliveSlot(2);
         TestKeepaliveCallback callback2 = new TestKeepaliveCallback();
         PacketKeepalive ka2 = mCm.startNattKeepalive(
                 myNet, validKaInterval, callback2, myIPv4, 6789, dstIPv4);
@@ -6500,7 +6917,7 @@
         ka.stop();
         callback.expectStopped();
 
-        mWiFiNetworkAgent.setExpectedKeepaliveSlot(1);
+        mWiFiAgent.setExpectedKeepaliveSlot(1);
         TestKeepaliveCallback callback3 = new TestKeepaliveCallback();
         PacketKeepalive ka3 = mCm.startNattKeepalive(
                 myNet, validKaInterval, callback3, myIPv4, 9876, dstIPv4);
@@ -6602,12 +7019,12 @@
         }
 
         // Check that a started keepalive can be stopped.
-        mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS);
+        mWiFiAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS);
         try (SocketKeepalive ka = mCm.createSocketKeepalive(
                 myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
             ka.start(validKaInterval);
             callback.expectStarted();
-            mWiFiNetworkAgent.setStopKeepaliveEvent(SocketKeepalive.SUCCESS);
+            mWiFiAgent.setStopKeepaliveEvent(SocketKeepalive.SUCCESS);
             ka.stop();
             callback.expectStopped();
 
@@ -6636,9 +7053,9 @@
             callback.expectStarted();
             bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25));
             bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25));
-            mWiFiNetworkAgent.sendLinkProperties(bogusLp);
+            mWiFiAgent.sendLinkProperties(bogusLp);
             callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
-            mWiFiNetworkAgent.sendLinkProperties(lp);
+            mWiFiAgent.sendLinkProperties(lp);
         }
 
         // Check that a started keepalive is stopped correctly when the network disconnects.
@@ -6646,21 +7063,20 @@
                 myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
             ka.start(validKaInterval);
             callback.expectStarted();
-            mWiFiNetworkAgent.disconnect();
-            mWiFiNetworkAgent.expectDisconnected();
+            mWiFiAgent.disconnect();
+            mWiFiAgent.expectDisconnected();
             callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
 
             // ... and that stopping it after that has no adverse effects.
             waitForIdle();
-            final Network myNetAlias = myNet;
-            assertNull(mCm.getNetworkCapabilities(myNetAlias));
+            assertNull(mCm.getNetworkCapabilities(myNet));
             ka.stop();
             callback.assertNoCallback();
         }
 
         // Reconnect.
         myNet = connectKeepaliveNetwork(lp);
-        mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS);
+        mWiFiAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS);
 
         // Check that a stop followed by network disconnects does not result in crash.
         try (SocketKeepalive ka = mCm.createSocketKeepalive(
@@ -6669,7 +7085,7 @@
             callback.expectStarted();
             // Delay the response of keepalive events in networkAgent long enough to make sure
             // the follow-up network disconnection will be processed first.
-            mWiFiNetworkAgent.setKeepaliveResponseDelay(3 * TIMEOUT_MS);
+            mWiFiAgent.setKeepaliveResponseDelay(3 * TIMEOUT_MS);
             ka.stop();
             // Call stop() twice shouldn't result in crash, b/182586681.
             ka.stop();
@@ -6679,8 +7095,8 @@
             waitForIdleSerialExecutor(executor, TIMEOUT_MS);
             waitForIdle();
 
-            mWiFiNetworkAgent.disconnect();
-            mWiFiNetworkAgent.expectDisconnected();
+            mWiFiAgent.disconnect();
+            mWiFiAgent.expectDisconnected();
             callback.expectStopped();
             callback.assertNoCallback();
         }
@@ -6688,20 +7104,18 @@
         // Reconnect.
         waitForIdle();
         myNet = connectKeepaliveNetwork(lp);
-        mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS);
+        mWiFiAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS);
 
         // Check that keepalive slots start from 1 and increment. The first one gets slot 1.
-        mWiFiNetworkAgent.setExpectedKeepaliveSlot(1);
-        int srcPort2 = 0;
+        mWiFiAgent.setExpectedKeepaliveSlot(1);
         try (SocketKeepalive ka = mCm.createSocketKeepalive(
                 myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
             ka.start(validKaInterval);
             callback.expectStarted();
 
             // The second one gets slot 2.
-            mWiFiNetworkAgent.setExpectedKeepaliveSlot(2);
+            mWiFiAgent.setExpectedKeepaliveSlot(2);
             final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket();
-            srcPort2 = testSocket2.getPort();
             TestSocketKeepaliveCallback callback2 = new TestSocketKeepaliveCallback(executor);
             try (SocketKeepalive ka2 = mCm.createSocketKeepalive(
                     myNet, testSocket2, myIPv4, dstIPv4, executor, callback2)) {
@@ -6724,9 +7138,9 @@
         // assertFalse(isUdpPortInUse(srcPort));
         // assertFalse(isUdpPortInUse(srcPort2));
 
-        mWiFiNetworkAgent.disconnect();
-        mWiFiNetworkAgent.expectDisconnected();
-        mWiFiNetworkAgent = null;
+        mWiFiAgent.disconnect();
+        mWiFiAgent.expectDisconnected();
+        mWiFiAgent = null;
     }
 
     @Test
@@ -6800,9 +7214,9 @@
         testSocketV4.close();
         testSocketV6.close();
 
-        mWiFiNetworkAgent.disconnect();
-        mWiFiNetworkAgent.expectDisconnected();
-        mWiFiNetworkAgent = null;
+        mWiFiAgent.disconnect();
+        mWiFiAgent.expectDisconnected();
+        mWiFiAgent = null;
     }
 
     private void doTestNattSocketKeepalivesFdWithExecutor(Executor executor) throws Exception {
@@ -6817,8 +7231,8 @@
         lp.addLinkAddress(new LinkAddress(myIPv4, 25));
         lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254")));
         Network myNet = connectKeepaliveNetwork(lp);
-        mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS);
-        mWiFiNetworkAgent.setStopKeepaliveEvent(SocketKeepalive.SUCCESS);
+        mWiFiAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS);
+        mWiFiAgent.setStopKeepaliveEvent(SocketKeepalive.SUCCESS);
 
         TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback(executor);
 
@@ -6853,9 +7267,9 @@
         // TODO: enable this check after ensuring a valid free port. See b/129512753#comment7.
         // assertFalse(isUdpPortInUse(srcPort));
 
-        mWiFiNetworkAgent.disconnect();
-        mWiFiNetworkAgent.expectDisconnected();
-        mWiFiNetworkAgent = null;
+        mWiFiAgent.disconnect();
+        mWiFiAgent.expectDisconnected();
+        mWiFiAgent = null;
     }
 
     private static boolean isUdpPortInUse(int port) {
@@ -6893,18 +7307,18 @@
     }
 
     private void assertPinnedToWifiWithCellDefault() {
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getBoundNetworkForProcess());
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getBoundNetworkForProcess());
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
     }
 
     private void assertPinnedToWifiWithWifiDefault() {
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getBoundNetworkForProcess());
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getBoundNetworkForProcess());
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
     }
 
     private void assertNotPinnedToWifi() {
         assertNull(mCm.getBoundNetworkForProcess());
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
     }
 
     @Test
@@ -6917,23 +7331,23 @@
         TestNetworkPinner.pin(mServiceContext, wifiRequest);
         assertNull(mCm.getBoundNetworkForProcess());
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);
 
         // When wi-fi connects, expect to be pinned.
         assertTrue(TestNetworkPinner.awaitPin(100));
         assertPinnedToWifiWithCellDefault();
 
         // Disconnect and expect the pin to drop.
-        mWiFiNetworkAgent.disconnect();
+        mWiFiAgent.disconnect();
         assertTrue(TestNetworkPinner.awaitUnpin(100));
         assertNotPinnedToWifi();
 
         // Reconnecting does not cause the pin to come back.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);
         assertFalse(TestNetworkPinner.awaitPin(100));
         assertNotPinnedToWifi();
 
@@ -6947,22 +7361,22 @@
         assertNotPinnedToWifi();
 
         // Disconnect cell and wifi.
-        ExpectedBroadcast b = registerConnectivityBroadcast(3);  // cell down, wifi up, wifi down.
-        mCellNetworkAgent.disconnect();
-        mWiFiNetworkAgent.disconnect();
+        ExpectedBroadcast b = expectConnectivityAction(3);  // cell down, wifi up, wifi down.
+        mCellAgent.disconnect();
+        mWiFiAgent.disconnect();
         b.expectBroadcast();
 
         // Pinning takes effect even if the pinned network is the default when the pin is set...
         TestNetworkPinner.pin(mServiceContext, wifiRequest);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);
         assertTrue(TestNetworkPinner.awaitPin(100));
         assertPinnedToWifiWithWifiDefault();
 
         // ... and is maintained even when that network is no longer the default.
-        b = registerConnectivityBroadcast(1);
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mCellNetworkAgent.connect(true);
+        b = expectConnectivityAction(1);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mCellAgent.connect(true);
         b.expectBroadcast();
         assertPinnedToWifiWithCellDefault();
     }
@@ -7137,7 +7551,7 @@
 
     @Test
     public void testNetworkInfoOfTypeNone() throws Exception {
-        ExpectedBroadcast b = registerConnectivityBroadcast(1);
+        ExpectedBroadcast b = expectConnectivityAction(1);
 
         verifyNoNetwork();
         TestNetworkAgentWrapper wifiAware = new TestNetworkAgentWrapper(TRANSPORT_WIFI_AWARE);
@@ -7155,7 +7569,7 @@
         mCm.registerNetworkCallback(request, callback);
 
         // Bring up wifi aware network.
-        wifiAware.connect(false, false, false /* isStrictMode */);
+        wifiAware.connect(false, false, false /* privateDnsProbeSent */);
         callback.expectAvailableCallbacksUnvalidated(wifiAware);
 
         assertNull(mCm.getActiveNetworkInfo());
@@ -7166,7 +7580,7 @@
 
         // Disconnect wifi aware network.
         wifiAware.disconnect();
-        callback.expect(CallbackEntry.LOST, TIMEOUT_MS);
+        callback.expect(LOST, TIMEOUT_MS);
         mCm.unregisterNetworkCallback(callback);
 
         verifyNoNetwork();
@@ -7213,13 +7627,12 @@
         // ConnectivityService.
         TestNetworkAgentWrapper networkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
         networkAgent.connect(true);
-        networkCallback.expect(CallbackEntry.AVAILABLE, networkAgent);
-        networkCallback.expect(CallbackEntry.NETWORK_CAPS_UPDATED, networkAgent);
+        networkCallback.expect(AVAILABLE, networkAgent);
+        networkCallback.expect(NETWORK_CAPS_UPDATED, networkAgent);
         CallbackEntry.LinkPropertiesChanged cbi =
-                networkCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED,
-                networkAgent);
-        networkCallback.expect(CallbackEntry.BLOCKED_STATUS, networkAgent);
-        networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, networkAgent);
+                networkCallback.expect(LINK_PROPERTIES_CHANGED, networkAgent);
+        networkCallback.expect(BLOCKED_STATUS, networkAgent);
+        networkCallback.expectCaps(networkAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
         networkCallback.assertNoCallback();
         checkDirectlyConnectedRoutes(cbi.getLp(), asList(myIpv4Address),
                 asList(myIpv4DefaultRoute));
@@ -7233,7 +7646,7 @@
         newLp.addLinkAddress(myIpv6Address1);
         newLp.addLinkAddress(myIpv6Address2);
         networkAgent.sendLinkProperties(newLp);
-        cbi = networkCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, networkAgent);
+        cbi = networkCallback.expect(LINK_PROPERTIES_CHANGED, networkAgent);
         networkCallback.assertNoCallback();
         checkDirectlyConnectedRoutes(cbi.getLp(),
                 asList(myIpv4Address, myIpv6Address1, myIpv6Address2),
@@ -7294,15 +7707,15 @@
     }
 
     private List<Network> onlyCell() {
-        return List.of(mCellNetworkAgent.getNetwork());
+        return List.of(mCellAgent.getNetwork());
     }
 
     private List<Network> onlyWifi() {
-        return List.of(mWiFiNetworkAgent.getNetwork());
+        return List.of(mWiFiAgent.getNetwork());
     }
 
     private List<Network> cellAndWifi() {
-        return List.of(mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork());
+        return List.of(mCellAgent.getNetwork(), mWiFiAgent.getNetwork());
     }
 
     @Test
@@ -7312,13 +7725,15 @@
         LinkProperties wifiLp = new LinkProperties();
         wifiLp.setInterfaceName(WIFI_IFNAME);
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
 
         // Simple connection with initial LP should have updated ifaces.
-        mCellNetworkAgent.connect(false);
+        mCellAgent.connect(false);
         waitForIdle();
-        expectNotifyNetworkStatus(onlyCell(), onlyCell(), MOBILE_IFNAME);
+        List<Network> allNetworks = mService.shouldCreateNetworksImmediately()
+                ? cellAndWifi() : onlyCell();
+        expectNotifyNetworkStatus(allNetworks, onlyCell(), MOBILE_IFNAME);
         reset(mStatsManager);
 
         // Verify change fields other than interfaces does not trigger a notification to NSS.
@@ -7326,13 +7741,13 @@
         cellLp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("192.0.2.4"),
                 MOBILE_IFNAME));
         cellLp.setDnsServers(List.of(InetAddress.getAllByName("8.8.8.8")));
-        mCellNetworkAgent.sendLinkProperties(cellLp);
+        mCellAgent.sendLinkProperties(cellLp);
         verifyNoMoreInteractions(mStatsManager);
         reset(mStatsManager);
 
         // Default network switch should update ifaces.
-        mWiFiNetworkAgent.connect(false);
-        mWiFiNetworkAgent.sendLinkProperties(wifiLp);
+        mWiFiAgent.connect(false);
+        mWiFiAgent.sendLinkProperties(wifiLp);
         waitForIdle();
         assertEquals(wifiLp, mService.getActiveLinkProperties());
         expectNotifyNetworkStatus(cellAndWifi(), onlyWifi(), WIFI_IFNAME);
@@ -7344,52 +7759,52 @@
         // notifyNetworkStatus is called again, traffic on that interface will be accounted to the
         // disconnected network. This is likely a bug in ConnectivityService; it should probably
         // call notifyNetworkStatus again without the disconnected network.
-        mCellNetworkAgent.disconnect();
+        mCellAgent.disconnect();
         waitForIdle();
         expectNotifyNetworkStatus(cellAndWifi(), onlyWifi(), WIFI_IFNAME);
         verifyNoMoreInteractions(mStatsManager);
         reset(mStatsManager);
 
         // Connecting a network updates ifaces even if the network doesn't become default.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
-        mCellNetworkAgent.connect(false);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mCellAgent.connect(false);
         waitForIdle();
         expectNotifyNetworkStatus(cellAndWifi(), onlyWifi(), WIFI_IFNAME);
         reset(mStatsManager);
 
         // Disconnect should update ifaces.
-        mWiFiNetworkAgent.disconnect();
+        mWiFiAgent.disconnect();
         waitForIdle();
         expectNotifyNetworkStatus(onlyCell(), onlyCell(), MOBILE_IFNAME);
         reset(mStatsManager);
 
         // Metered change should update ifaces
-        mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        mCellAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
         waitForIdle();
         expectNotifyNetworkStatus(onlyCell(), onlyCell(), MOBILE_IFNAME);
         reset(mStatsManager);
 
-        mCellNetworkAgent.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        mCellAgent.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
         waitForIdle();
         expectNotifyNetworkStatus(onlyCell(), onlyCell(), MOBILE_IFNAME);
         reset(mStatsManager);
 
         // Temp metered change shouldn't update ifaces
-        mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+        mCellAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
         waitForIdle();
         verify(mStatsManager, never()).notifyNetworkStatus(eq(onlyCell()),
                 any(List.class), eq(MOBILE_IFNAME), any(List.class));
         reset(mStatsManager);
 
         // Congested change shouldn't update ifaces
-        mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
+        mCellAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
         waitForIdle();
         verify(mStatsManager, never()).notifyNetworkStatus(eq(onlyCell()),
                 any(List.class), eq(MOBILE_IFNAME), any(List.class));
         reset(mStatsManager);
 
         // Roaming change should update ifaces
-        mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
+        mCellAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
         waitForIdle();
         expectNotifyNetworkStatus(onlyCell(), onlyCell(), MOBILE_IFNAME);
         reset(mStatsManager);
@@ -7401,25 +7816,22 @@
         mMockVpn.establishForMyUid(lp);
         assertUidRangesUpdatedForMyUid(true);
 
-        final List<Network> cellAndVpn =
-                List.of(mCellNetworkAgent.getNetwork(), mMockVpn.getNetwork());
+        final List<Network> cellAndVpn = List.of(mCellAgent.getNetwork(), mMockVpn.getNetwork());
 
         // A VPN with default (null) underlying networks sets the underlying network's interfaces...
         expectNotifyNetworkStatus(cellAndVpn, cellAndVpn, MOBILE_IFNAME, Process.myUid(),
                 VPN_IFNAME, List.of(MOBILE_IFNAME));
 
         // ...and updates them as the default network switches.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);
-        mWiFiNetworkAgent.sendLinkProperties(wifiLp);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);
+        mWiFiAgent.sendLinkProperties(wifiLp);
         final Network[] onlyNull = new Network[]{null};
-        final List<Network> wifiAndVpn =
-                List.of(mWiFiNetworkAgent.getNetwork(), mMockVpn.getNetwork());
-        final List<Network> cellWifiAndVpn =
-                List.of(mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork(),
-                        mMockVpn.getNetwork());
+        final List<Network> wifiAndVpn = List.of(mWiFiAgent.getNetwork(), mMockVpn.getNetwork());
+        final List<Network> cellWifiAndVpn = List.of(mCellAgent.getNetwork(),
+                mWiFiAgent.getNetwork(), mMockVpn.getNetwork());
         final Network[] cellNullAndWifi =
-                new Network[]{mCellNetworkAgent.getNetwork(), null, mWiFiNetworkAgent.getNetwork()};
+                new Network[] { mCellAgent.getNetwork(), null, mWiFiAgent.getNetwork() };
 
         waitForIdle();
         assertEquals(wifiLp, mService.getActiveLinkProperties());
@@ -7459,9 +7871,9 @@
         // could result in incorrect data usage measurements if the interface used by the
         // disconnected network is reused by a system component that does not register an agent for
         // it (e.g., tethering).
-        mCellNetworkAgent.disconnect();
+        mCellAgent.disconnect();
         waitForIdle();
-        assertNull(mService.getLinkProperties(mCellNetworkAgent.getNetwork()));
+        assertNull(mService.getLinkProperties(mCellAgent.getNetwork()));
         expectNotifyNetworkStatus(cellWifiAndVpn, wifiAndVpn, MOBILE_IFNAME, Process.myUid(),
                 VPN_IFNAME, List.of(MOBILE_IFNAME, WIFI_IFNAME));
 
@@ -7477,16 +7889,16 @@
         // ... but if something else happens that causes notifyIfacesChangedForNetworkStats to be
         // called again, it does. For example, connect Ethernet, but with a low score, such that it
         // does not become the default network.
-        mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
-        mEthernetNetworkAgent.setScore(
+        mEthernetAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
+        mEthernetAgent.setScore(
                 new NetworkScore.Builder().setLegacyInt(30).setExiting(true).build());
-        mEthernetNetworkAgent.connect(false);
+        mEthernetAgent.connect(false);
         waitForIdle();
         verify(mStatsManager).notifyNetworkStatus(any(List.class),
                 any(List.class), any() /* anyString() doesn't match null */,
                 argThat(vpnInfos -> vpnInfos.get(0).getUnderlyingInterfaces().size() == 1
                         && WIFI_IFNAME.equals(vpnInfos.get(0).getUnderlyingInterfaces().get(0))));
-        mEthernetNetworkAgent.disconnect();
+        mEthernetAgent.disconnect();
         waitForIdle();
         reset(mStatsManager);
 
@@ -7526,18 +7938,17 @@
         final int[] adminUids = new int[] {Process.myUid() + 1};
         final NetworkCapabilities ncTemplate = new NetworkCapabilities();
         ncTemplate.setAdministratorUids(adminUids);
-        mCellNetworkAgent =
+        mCellAgent =
                 new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(), ncTemplate);
-        mCellNetworkAgent.connect(false /* validated */);
+        mCellAgent.connect(false /* validated */);
 
         // Verify case where caller has permission
         mServiceContext.setPermission(
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_GRANTED);
         TestNetworkCallback callback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(callback);
-        callback.expect(CallbackEntry.AVAILABLE, mCellNetworkAgent);
-        callback.expectCapabilitiesThat(
-                mCellNetworkAgent, nc -> Arrays.equals(adminUids, nc.getAdministratorUids()));
+        callback.expect(AVAILABLE, mCellAgent);
+        callback.expectCaps(mCellAgent, c -> Arrays.equals(adminUids, c.getAdministratorUids()));
         mCm.unregisterNetworkCallback(callback);
 
         // Verify case where caller does NOT have permission
@@ -7546,9 +7957,8 @@
         mServiceContext.setPermission(NETWORK_STACK, PERMISSION_DENIED);
         callback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(callback);
-        callback.expect(CallbackEntry.AVAILABLE, mCellNetworkAgent);
-        callback.expectCapabilitiesThat(
-                mCellNetworkAgent, nc -> nc.getAdministratorUids().length == 0);
+        callback.expect(AVAILABLE, mCellAgent);
+        callback.expectCaps(mCellAgent, c -> c.getAdministratorUids().length == 0);
     }
 
     @Test
@@ -7565,10 +7975,10 @@
         // Connect a VCN-managed wifi network.
         final LinkProperties wifiLp = new LinkProperties();
         wifiLp.setInterfaceName(WIFI_IFNAME);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
-        mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
-        mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
-        mWiFiNetworkAgent.connect(true /* validated */);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiAgent.removeCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        mWiFiAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        mWiFiAgent.connect(true /* validated */);
 
         final List<Network> none = List.of();
         expectNotifyNetworkStatus(onlyWifi(), none, null);  // Wifi is not the default network
@@ -7583,7 +7993,7 @@
         final LinkProperties lp = new LinkProperties();
         lp.setInterfaceName(vcnIface);
         final TestNetworkAgentWrapper vcn = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, lp, nc);
-        vcn.setUnderlyingNetworks(List.of(mWiFiNetworkAgent.getNetwork()));
+        vcn.setUnderlyingNetworks(List.of(mWiFiAgent.getNetwork()));
         vcn.connect(false /* validated */);
 
         final TestNetworkCallback callback = new TestNetworkCallback();
@@ -7596,12 +8006,12 @@
         assertFalse(nc.hasTransport(TRANSPORT_WIFI));
         assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
         final List<Network> onlyVcn = List.of(vcn.getNetwork());
-        final List<Network> vcnAndWifi = List.of(vcn.getNetwork(), mWiFiNetworkAgent.getNetwork());
+        final List<Network> vcnAndWifi = List.of(vcn.getNetwork(), mWiFiAgent.getNetwork());
         expectNotifyNetworkStatus(vcnAndWifi, onlyVcn, vcnIface, ownerUid, vcnIface,
                 List.of(WIFI_IFNAME));
 
         // Add NOT_METERED to the underlying network, check that it is not propagated.
-        mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        mWiFiAgent.addCapability(NET_CAPABILITY_NOT_METERED);
         callback.assertNoCallback();
         nc = mCm.getNetworkCapabilities(vcn.getNetwork());
         assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED));
@@ -7609,11 +8019,11 @@
         // Switch underlying networks.
         final LinkProperties cellLp = new LinkProperties();
         cellLp.setInterfaceName(MOBILE_IFNAME);
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
-        mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
-        mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_ROAMING);
-        mCellNetworkAgent.connect(false /* validated */);
-        vcn.setUnderlyingNetworks(List.of(mCellNetworkAgent.getNetwork()));
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mCellAgent.removeCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+        mCellAgent.addCapability(NET_CAPABILITY_NOT_ROAMING);
+        mCellAgent.connect(false /* validated */);
+        vcn.setUnderlyingNetworks(List.of(mCellAgent.getNetwork()));
 
         // The underlying capability changes do not propagate to the virtual network, but
         // NetworkStatsService is informed of the new underlying interface.
@@ -7622,7 +8032,7 @@
         assertFalse(nc.hasTransport(TRANSPORT_WIFI));
         assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_ROAMING));
         final List<Network> vcnWifiAndCell = List.of(vcn.getNetwork(),
-                mWiFiNetworkAgent.getNetwork(), mCellNetworkAgent.getNetwork());
+                mWiFiAgent.getNetwork(), mCellAgent.getNetwork());
         expectNotifyNetworkStatus(vcnWifiAndCell, onlyVcn, vcnIface, ownerUid, vcnIface,
                 List.of(MOBILE_IFNAME));
     }
@@ -7631,10 +8041,14 @@
     public void testBasicDnsConfigurationPushed() throws Exception {
         setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com");
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        final int netId = mCellAgent.getNetwork().netId;
         waitForIdle();
-        verify(mMockDnsResolver, never()).setResolverConfiguration(any());
-        verifyNoMoreInteractions(mMockDnsResolver);
+        if (mService.shouldCreateNetworksImmediately()) {
+            verify(mMockDnsResolver, times(1)).createNetworkCache(netId);
+        } else {
+            verify(mMockDnsResolver, never()).setResolverConfiguration(any());
+        }
 
         final LinkProperties cellLp = new LinkProperties();
         cellLp.setInterfaceName(MOBILE_IFNAME);
@@ -7647,18 +8061,20 @@
         cellLp.addLinkAddress(new LinkAddress("2001:db8:1::1/64"));
         cellLp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("2001:db8:1::1"),
                 MOBILE_IFNAME));
-        mCellNetworkAgent.sendLinkProperties(cellLp);
-        mCellNetworkAgent.connect(false);
+        mCellAgent.sendLinkProperties(cellLp);
+        mCellAgent.connect(false);
         waitForIdle();
-
-        verify(mMockDnsResolver, times(1)).createNetworkCache(
-                eq(mCellNetworkAgent.getNetwork().netId));
-        // CS tells dnsresolver about the empty DNS config for this network.
+        if (!mService.shouldCreateNetworksImmediately()) {
+            // CS tells dnsresolver about the empty DNS config for this network.
+            verify(mMockDnsResolver, times(1)).createNetworkCache(netId);
+        }
         verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(any());
+
+        verifyNoMoreInteractions(mMockDnsResolver);
         reset(mMockDnsResolver);
 
         cellLp.addDnsServer(InetAddress.getByName("2001:db8::1"));
-        mCellNetworkAgent.sendLinkProperties(cellLp);
+        mCellAgent.sendLinkProperties(cellLp);
         waitForIdle();
         verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(
                 mResolverParamsParcelCaptor.capture());
@@ -7670,7 +8086,7 @@
         reset(mMockDnsResolver);
 
         cellLp.addDnsServer(InetAddress.getByName("192.0.2.1"));
-        mCellNetworkAgent.sendLinkProperties(cellLp);
+        mCellAgent.sendLinkProperties(cellLp);
         waitForIdle();
         verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(
                 mResolverParamsParcelCaptor.capture());
@@ -7688,7 +8104,7 @@
         final String TLS_SERVER6 = "2001:db8:53::53";
         final InetAddress[] TLS_IPS = new InetAddress[]{ InetAddress.getByName(TLS_SERVER6) };
         final String[] TLS_SERVERS = new String[]{ TLS_SERVER6 };
-        mCellNetworkAgent.mNmCallbacks.notifyPrivateDnsConfigResolved(
+        mCellAgent.mNmCallbacks.notifyPrivateDnsConfigResolved(
                 new PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS).toParcel());
 
         waitForIdle();
@@ -7709,11 +8125,10 @@
         final TestNetworkCallback callback = new TestNetworkCallback();
         mCm.registerNetworkCallback(request, callback);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        verify(mMockDnsResolver, times(1)).createNetworkCache(
-                eq(mWiFiNetworkAgent.getNetwork().netId));
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        verify(mMockDnsResolver, times(1)).createNetworkCache(eq(mWiFiAgent.getNetwork().netId));
         verify(mMockDnsResolver, times(2)).setResolverConfiguration(
                 mResolverParamsParcelCaptor.capture());
         final ResolverParamsParcel resolverParams = mResolverParamsParcelCaptor.getValue();
@@ -7729,31 +8144,31 @@
         TestNetworkCallback callback = new TestNetworkCallback();
         mCm.registerNetworkCallback(request, callback);
         // Bring up wifi.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         // Private DNS resolution failed, checking if the notification will be shown or not.
-        mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */);
-        mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+        mWiFiAgent.setNetworkInvalid(true /* invalidBecauseOfPrivateDns */);
+        mWiFiAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
         waitForIdle();
         // If network validation failed, NetworkMonitor will re-evaluate the network.
         // ConnectivityService should filter the redundant notification. This part is trying to
         // simulate that situation and check if ConnectivityService could filter that case.
-        mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+        mWiFiAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
         waitForIdle();
         verify(mNotificationManager, timeout(TIMEOUT_MS).times(1)).notify(anyString(),
                 eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), any());
         // If private DNS resolution successful, the PRIVATE_DNS_BROKEN notification shouldn't be
         // shown.
-        mWiFiNetworkAgent.setNetworkValid(true /* isStrictMode */);
-        mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+        mWiFiAgent.setNetworkValid(true /* privateDnsProbeSent */);
+        mWiFiAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
         waitForIdle();
         verify(mNotificationManager, timeout(TIMEOUT_MS).times(1)).cancel(anyString(),
                 eq(NotificationType.PRIVATE_DNS_BROKEN.eventId));
         // If private DNS resolution failed again, the PRIVATE_DNS_BROKEN notification should be
         // shown again.
-        mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */);
-        mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+        mWiFiAgent.setNetworkInvalid(true /* invalidBecauseOfPrivateDns */);
+        mWiFiAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
         waitForIdle();
         verify(mNotificationManager, timeout(TIMEOUT_MS).times(2)).notify(anyString(),
                 eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), any());
@@ -7769,11 +8184,14 @@
                 .addTransportType(TRANSPORT_CELLULAR).build();
         mCm.requestNetwork(cellRequest, cellNetworkCallback);
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        final int netId = mCellAgent.getNetwork().netId;
         waitForIdle();
-        // CS tells netd about the empty DNS config for this network.
-        verify(mMockDnsResolver, never()).setResolverConfiguration(any());
-        verifyNoMoreInteractions(mMockDnsResolver);
+        if (mService.shouldCreateNetworksImmediately()) {
+            verify(mMockDnsResolver, times(1)).createNetworkCache(netId);
+        } else {
+            verify(mMockDnsResolver, never()).setResolverConfiguration(any());
+        }
 
         final LinkProperties cellLp = new LinkProperties();
         cellLp.setInterfaceName(MOBILE_IFNAME);
@@ -7789,11 +8207,12 @@
         cellLp.addDnsServer(InetAddress.getByName("2001:db8::1"));
         cellLp.addDnsServer(InetAddress.getByName("192.0.2.1"));
 
-        mCellNetworkAgent.sendLinkProperties(cellLp);
-        mCellNetworkAgent.connect(false);
+        mCellAgent.sendLinkProperties(cellLp);
+        mCellAgent.connect(false);
         waitForIdle();
-        verify(mMockDnsResolver, times(1)).createNetworkCache(
-                eq(mCellNetworkAgent.getNetwork().netId));
+        if (!mService.shouldCreateNetworksImmediately()) {
+            verify(mMockDnsResolver, times(1)).createNetworkCache(netId);
+        }
         verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(
                 mResolverParamsParcelCaptor.capture());
         ResolverParamsParcel resolvrParams = mResolverParamsParcelCaptor.getValue();
@@ -7804,13 +8223,13 @@
         assertEquals(2, resolvrParams.tlsServers.length);
         assertTrue(new ArraySet<>(resolvrParams.tlsServers).containsAll(
                 asList("2001:db8::1", "192.0.2.1")));
+        verifyNoMoreInteractions(mMockDnsResolver);
         reset(mMockDnsResolver);
-        cellNetworkCallback.expect(CallbackEntry.AVAILABLE, mCellNetworkAgent);
-        cellNetworkCallback.expect(CallbackEntry.NETWORK_CAPS_UPDATED,
-                mCellNetworkAgent);
+        cellNetworkCallback.expect(AVAILABLE, mCellAgent);
+        cellNetworkCallback.expect(NETWORK_CAPS_UPDATED, mCellAgent);
         CallbackEntry.LinkPropertiesChanged cbi = cellNetworkCallback.expect(
-                CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
-        cellNetworkCallback.expect(CallbackEntry.BLOCKED_STATUS, mCellNetworkAgent);
+                LINK_PROPERTIES_CHANGED, mCellAgent);
+        cellNetworkCallback.expect(BLOCKED_STATUS, mCellAgent);
         cellNetworkCallback.assertNoCallback();
         assertFalse(cbi.getLp().isPrivateDnsActive());
         assertNull(cbi.getLp().getPrivateDnsServerName());
@@ -7841,8 +8260,7 @@
         setPrivateDnsSettings(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, "strict.example.com");
         // Can't test dns configuration for strict mode without properly mocking
         // out the DNS lookups, but can test that LinkProperties is updated.
-        cbi = cellNetworkCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED,
-                mCellNetworkAgent);
+        cbi = cellNetworkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
         cellNetworkCallback.assertNoCallback();
         assertTrue(cbi.getLp().isPrivateDnsActive());
         assertEquals("strict.example.com", cbi.getLp().getPrivateDnsServerName());
@@ -7868,18 +8286,17 @@
                 .addTransportType(TRANSPORT_CELLULAR).build();
         mCm.requestNetwork(cellRequest, cellNetworkCallback);
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         waitForIdle();
         LinkProperties lp = new LinkProperties();
-        mCellNetworkAgent.sendLinkProperties(lp);
-        mCellNetworkAgent.connect(false);
+        mCellAgent.sendLinkProperties(lp);
+        mCellAgent.connect(false);
         waitForIdle();
-        cellNetworkCallback.expect(CallbackEntry.AVAILABLE, mCellNetworkAgent);
-        cellNetworkCallback.expect(CallbackEntry.NETWORK_CAPS_UPDATED,
-                mCellNetworkAgent);
+        cellNetworkCallback.expect(AVAILABLE, mCellAgent);
+        cellNetworkCallback.expect(NETWORK_CAPS_UPDATED, mCellAgent);
         CallbackEntry.LinkPropertiesChanged cbi = cellNetworkCallback.expect(
-                CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
-        cellNetworkCallback.expect(CallbackEntry.BLOCKED_STATUS, mCellNetworkAgent);
+                LINK_PROPERTIES_CHANGED, mCellAgent);
+        cellNetworkCallback.expect(BLOCKED_STATUS, mCellAgent);
         cellNetworkCallback.assertNoCallback();
         assertFalse(cbi.getLp().isPrivateDnsActive());
         assertNull(cbi.getLp().getPrivateDnsServerName());
@@ -7889,16 +8306,15 @@
         // Send a validation event for a server that is not part of the current
         // resolver config. The validation event should be ignored.
         mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent(
-                makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId, "",
+                makePrivateDnsValidationEvent(mCellAgent.getNetwork().netId, "",
                         "145.100.185.18", VALIDATION_RESULT_SUCCESS));
         cellNetworkCallback.assertNoCallback();
 
         // Add a dns server to the LinkProperties.
         LinkProperties lp2 = new LinkProperties(lp);
         lp2.addDnsServer(InetAddress.getByName("145.100.185.16"));
-        mCellNetworkAgent.sendLinkProperties(lp2);
-        cbi = cellNetworkCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED,
-                mCellNetworkAgent);
+        mCellAgent.sendLinkProperties(lp2);
+        cbi = cellNetworkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
         cellNetworkCallback.assertNoCallback();
         assertFalse(cbi.getLp().isPrivateDnsActive());
         assertNull(cbi.getLp().getPrivateDnsServerName());
@@ -7908,13 +8324,13 @@
         // Send a validation event containing a hostname that is not part of
         // the current resolver config. The validation event should be ignored.
         mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent(
-                makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId,
+                makePrivateDnsValidationEvent(mCellAgent.getNetwork().netId,
                         "145.100.185.16", "hostname", VALIDATION_RESULT_SUCCESS));
         cellNetworkCallback.assertNoCallback();
 
         // Send a validation event where validation failed.
         mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent(
-                makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId,
+                makePrivateDnsValidationEvent(mCellAgent.getNetwork().netId,
                         "145.100.185.16", "", VALIDATION_RESULT_FAILURE));
         cellNetworkCallback.assertNoCallback();
 
@@ -7922,10 +8338,9 @@
         // the current resolver config. A LinkProperties callback with updated
         // private dns fields should be sent.
         mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent(
-                makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId,
+                makePrivateDnsValidationEvent(mCellAgent.getNetwork().netId,
                         "145.100.185.16", "", VALIDATION_RESULT_SUCCESS));
-        cbi = cellNetworkCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED,
-                mCellNetworkAgent);
+        cbi = cellNetworkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
         cellNetworkCallback.assertNoCallback();
         assertTrue(cbi.getLp().isPrivateDnsActive());
         assertNull(cbi.getLp().getPrivateDnsServerName());
@@ -7935,9 +8350,8 @@
         // the network agent sends unrelated changes.
         LinkProperties lp3 = new LinkProperties(lp2);
         lp3.setMtu(1300);
-        mCellNetworkAgent.sendLinkProperties(lp3);
-        cbi = cellNetworkCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED,
-                mCellNetworkAgent);
+        mCellAgent.sendLinkProperties(lp3);
+        cbi = cellNetworkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
         cellNetworkCallback.assertNoCallback();
         assertTrue(cbi.getLp().isPrivateDnsActive());
         assertNull(cbi.getLp().getPrivateDnsServerName());
@@ -7948,9 +8362,8 @@
         // fields in LinkProperties.
         LinkProperties lp4 = new LinkProperties(lp3);
         lp4.removeDnsServer(InetAddress.getByName("145.100.185.16"));
-        mCellNetworkAgent.sendLinkProperties(lp4);
-        cbi = cellNetworkCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED,
-                mCellNetworkAgent);
+        mCellAgent.sendLinkProperties(lp4);
+        cbi = cellNetworkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
         cellNetworkCallback.assertNoCallback();
         assertFalse(cbi.getLp().isPrivateDnsActive());
         assertNull(cbi.getLp().getPrivateDnsServerName());
@@ -7985,10 +8398,10 @@
 
     @Test
     public void testApplyUnderlyingCapabilities() throws Exception {
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mCellNetworkAgent.connect(false /* validated */);
-        mWiFiNetworkAgent.connect(false /* validated */);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mCellAgent.connect(false /* validated */);
+        mWiFiAgent.connect(false /* validated */);
 
         final NetworkCapabilities cellNc = new NetworkCapabilities()
                 .addTransportType(TRANSPORT_CELLULAR)
@@ -8005,12 +8418,12 @@
                 .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
                 .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
                 .setLinkUpstreamBandwidthKbps(20);
-        mCellNetworkAgent.setNetworkCapabilities(cellNc, true /* sendToConnectivityService */);
-        mWiFiNetworkAgent.setNetworkCapabilities(wifiNc, true /* sendToConnectivityService */);
+        mCellAgent.setNetworkCapabilities(cellNc, true /* sendToConnectivityService */);
+        mWiFiAgent.setNetworkCapabilities(wifiNc, true /* sendToConnectivityService */);
         waitForIdle();
 
-        final Network mobile = mCellNetworkAgent.getNetwork();
-        final Network wifi = mWiFiNetworkAgent.getNetwork();
+        final Network mobile = mCellAgent.getNetwork();
+        final Network wifi = mWiFiAgent.getNetwork();
 
         final NetworkCapabilities initialCaps = new NetworkCapabilities();
         initialCaps.addTransportType(TRANSPORT_VPN);
@@ -8141,9 +8554,7 @@
             mMockVpn.setUnderlyingNetworks(new Network[]{wifiNetwork});
             // onCapabilitiesChanged() should be called because
             // NetworkCapabilities#mUnderlyingNetworks is updated.
-            CallbackEntry ce = callback.expect(CallbackEntry.NETWORK_CAPS_UPDATED,
-                    mMockVpn);
-            final NetworkCapabilities vpnNc1 = ((CallbackEntry.CapabilitiesChanged) ce).getCaps();
+            final NetworkCapabilities vpnNc1 = callback.expectCaps(mMockVpn);
             // Since the wifi network hasn't brought up,
             // ConnectivityService#applyUnderlyingCapabilities cannot find it. Update
             // NetworkCapabilities#mUnderlyingNetworks to an empty array, and it will be updated to
@@ -8165,9 +8576,9 @@
 
             // Make that underlying network connect, and expect to see its capabilities immediately
             // reflected in the VPN's capabilities.
-            mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-            assertEquals(wifiNetwork, mWiFiNetworkAgent.getNetwork());
-            mWiFiNetworkAgent.connect(false);
+            mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+            assertEquals(wifiNetwork, mWiFiAgent.getNetwork());
+            mWiFiAgent.connect(false);
             // TODO: the callback for the VPN happens before any callbacks are called for the wifi
             // network that has just connected. There appear to be two issues here:
             // 1. The VPN code will accept an underlying network as soon as getNetworkCapabilities()
@@ -8178,23 +8589,22 @@
             // 2. When a network connects, updateNetworkInfo propagates underlying network
             //    capabilities before rematching networks.
             // Given that this scenario can't really happen, this is probably fine for now.
-            ce = callback.expect(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
-            final NetworkCapabilities vpnNc2 = ((CallbackEntry.CapabilitiesChanged) ce).getCaps();
+            final NetworkCapabilities vpnNc2 = callback.expectCaps(mMockVpn);
             // The wifi network is brought up, NetworkCapabilities#mUnderlyingNetworks is updated to
             // it.
             underlyingNetwork.add(wifiNetwork);
             assertEquals(underlyingNetwork, vpnNc2.getUnderlyingNetworks());
-            callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+            callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
             assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
                     .hasTransport(TRANSPORT_VPN));
             assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
                     .hasTransport(TRANSPORT_WIFI));
 
             // Disconnect the network, and expect to see the VPN capabilities change accordingly.
-            mWiFiNetworkAgent.disconnect();
-            callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-            callback.expectCapabilitiesThat(mMockVpn, (nc) ->
-                    nc.getTransportTypes().length == 1 && nc.hasTransport(TRANSPORT_VPN));
+            mWiFiAgent.disconnect();
+            callback.expect(LOST, mWiFiAgent);
+            callback.expectCaps(mMockVpn, c -> c.getTransportTypes().length == 1
+                            && c.hasTransport(TRANSPORT_VPN));
 
             mMockVpn.disconnect();
             mCm.unregisterNetworkCallback(callback);
@@ -8214,15 +8624,14 @@
 
         // Connect a VPN.
         mMockVpn.establishForMyUid(false /* validated */, true /* hasInternet */,
-                false /* isStrictMode */);
+                false /* privateDnsProbeSent */);
         callback.expectAvailableCallbacksUnvalidated(mMockVpn);
 
         // Connect cellular data.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(false /* validated */);
-        callback.expectCapabilitiesThat(mMockVpn,
-                nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
-                        && nc.hasTransport(TRANSPORT_CELLULAR));
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(false /* validated */);
+        callback.expectCaps(mMockVpn, c -> c.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
+                && c.hasTransport(TRANSPORT_CELLULAR));
         callback.assertNoCallback();
 
         assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
@@ -8234,11 +8643,10 @@
         assertGetNetworkInfoOfGetActiveNetworkIsConnected(true);
 
         // Suspend the cellular network and expect the VPN to be suspended.
-        mCellNetworkAgent.suspend();
-        callback.expectCapabilitiesThat(mMockVpn,
-                nc -> !nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
-                        && nc.hasTransport(TRANSPORT_CELLULAR));
-        callback.expect(CallbackEntry.SUSPENDED, mMockVpn);
+        mCellAgent.suspend();
+        callback.expectCaps(mMockVpn, c -> !c.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
+                && c.hasTransport(TRANSPORT_CELLULAR));
+        callback.expect(SUSPENDED, mMockVpn);
         callback.assertNoCallback();
 
         assertFalse(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
@@ -8251,12 +8659,11 @@
         assertGetNetworkInfoOfGetActiveNetworkIsConnected(false);
 
         // Switch to another network. The VPN should no longer be suspended.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false /* validated */);
-        callback.expectCapabilitiesThat(mMockVpn,
-                nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
-                        && nc.hasTransport(TRANSPORT_WIFI));
-        callback.expect(CallbackEntry.RESUMED, mMockVpn);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false /* validated */);
+        callback.expectCaps(mMockVpn, c -> c.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
+                && c.hasTransport(TRANSPORT_WIFI));
+        callback.expect(RESUMED, mMockVpn);
         callback.assertNoCallback();
 
         assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
@@ -8268,16 +8675,14 @@
         assertGetNetworkInfoOfGetActiveNetworkIsConnected(true);
 
         // Unsuspend cellular and then switch back to it. The VPN remains not suspended.
-        mCellNetworkAgent.resume();
+        mCellAgent.resume();
         callback.assertNoCallback();
-        mWiFiNetworkAgent.disconnect();
-        callback.expectCapabilitiesThat(mMockVpn,
-                nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
-                        && nc.hasTransport(TRANSPORT_CELLULAR));
+        mWiFiAgent.disconnect();
+        callback.expectCaps(mMockVpn, c -> c.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
+                && c.hasTransport(TRANSPORT_CELLULAR));
         // Spurious double callback?
-        callback.expectCapabilitiesThat(mMockVpn,
-                nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
-                        && nc.hasTransport(TRANSPORT_CELLULAR));
+        callback.expectCaps(mMockVpn, c -> c.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
+                && c.hasTransport(TRANSPORT_CELLULAR));
         callback.assertNoCallback();
 
         assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
@@ -8289,11 +8694,10 @@
         assertGetNetworkInfoOfGetActiveNetworkIsConnected(true);
 
         // Suspend cellular and expect no connectivity.
-        mCellNetworkAgent.suspend();
-        callback.expectCapabilitiesThat(mMockVpn,
-                nc -> !nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
-                        && nc.hasTransport(TRANSPORT_CELLULAR));
-        callback.expect(CallbackEntry.SUSPENDED, mMockVpn);
+        mCellAgent.suspend();
+        callback.expectCaps(mMockVpn, c -> !c.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
+                && c.hasTransport(TRANSPORT_CELLULAR));
+        callback.expect(SUSPENDED, mMockVpn);
         callback.assertNoCallback();
 
         assertFalse(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
@@ -8305,11 +8709,10 @@
         assertGetNetworkInfoOfGetActiveNetworkIsConnected(false);
 
         // Resume cellular and expect that connectivity comes back.
-        mCellNetworkAgent.resume();
-        callback.expectCapabilitiesThat(mMockVpn,
-                nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
-                        && nc.hasTransport(TRANSPORT_CELLULAR));
-        callback.expect(CallbackEntry.RESUMED, mMockVpn);
+        mCellAgent.resume();
+        callback.expectCaps(mMockVpn, c -> c.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
+                && c.hasTransport(TRANSPORT_CELLULAR));
+        callback.expect(RESUMED, mMockVpn);
         callback.assertNoCallback();
 
         assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
@@ -8348,14 +8751,14 @@
                 new Handler(ConnectivityThread.getInstanceLooper()));
         defaultCallback.assertNoCallback();
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false);
 
-        genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        genericNotVpnNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        genericNotVpnNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         vpnNetworkCallback.assertNoCallback();
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
@@ -8370,7 +8773,7 @@
                 NetworkAgentConfigShimImpl.newInstance(mMockVpn.getNetworkAgentConfig())
                         .isVpnValidationRequired(),
                 mMockVpn.getAgent().getNetworkCapabilities()));
-        mMockVpn.getAgent().setNetworkValid(false /* isStrictMode */);
+        mMockVpn.getAgent().setNetworkValid(false /* privateDnsProbeSent */);
 
         mMockVpn.connect(false);
 
@@ -8381,16 +8784,15 @@
         defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
         systemDefaultCallback.assertNoCallback();
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
-        assertEquals(mWiFiNetworkAgent.getNetwork(),
-                systemDefaultCallback.getLastAvailableNetwork());
+        assertEquals(mWiFiAgent.getNetwork(), systemDefaultCallback.getLastAvailableNetwork());
 
         ranges.clear();
         mMockVpn.setUids(ranges);
 
-        genericNetworkCallback.expect(CallbackEntry.LOST, mMockVpn);
+        genericNetworkCallback.expect(LOST, mMockVpn);
         genericNotVpnNetworkCallback.assertNoCallback();
         wifiNetworkCallback.assertNoCallback();
-        vpnNetworkCallback.expect(CallbackEntry.LOST, mMockVpn);
+        vpnNetworkCallback.expect(LOST, mMockVpn);
 
         // TODO : The default network callback should actually get a LOST call here (also see the
         // comment below for AVAILABLE). This is because ConnectivityService does not look at UID
@@ -8398,7 +8800,7 @@
         // can't currently update their UIDs without disconnecting, so this does not matter too
         // much, but that is the reason the test here has to check for an update to the
         // capabilities instead of the expected LOST then AVAILABLE.
-        defaultCallback.expect(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
+        defaultCallback.expectCaps(mMockVpn);
         systemDefaultCallback.assertNoCallback();
 
         ranges.add(new UidRange(uid, uid));
@@ -8410,25 +8812,25 @@
         vpnNetworkCallback.expectAvailableCallbacksValidated(mMockVpn);
         // TODO : Here like above, AVAILABLE would be correct, but because this can't actually
         // happen outside of the test, ConnectivityService does not rematch callbacks.
-        defaultCallback.expect(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
+        defaultCallback.expectCaps(mMockVpn);
         systemDefaultCallback.assertNoCallback();
 
-        mWiFiNetworkAgent.disconnect();
+        mWiFiAgent.disconnect();
 
-        genericNetworkCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        genericNotVpnNetworkCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        wifiNetworkCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        genericNetworkCallback.expect(LOST, mWiFiAgent);
+        genericNotVpnNetworkCallback.expect(LOST, mWiFiAgent);
+        wifiNetworkCallback.expect(LOST, mWiFiAgent);
         vpnNetworkCallback.assertNoCallback();
         defaultCallback.assertNoCallback();
-        systemDefaultCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        systemDefaultCallback.expect(LOST, mWiFiAgent);
 
         mMockVpn.disconnect();
 
-        genericNetworkCallback.expect(CallbackEntry.LOST, mMockVpn);
+        genericNetworkCallback.expect(LOST, mMockVpn);
         genericNotVpnNetworkCallback.assertNoCallback();
         wifiNetworkCallback.assertNoCallback();
-        vpnNetworkCallback.expect(CallbackEntry.LOST, mMockVpn);
-        defaultCallback.expect(CallbackEntry.LOST, mMockVpn);
+        vpnNetworkCallback.expect(LOST, mMockVpn);
+        defaultCallback.expect(LOST, mMockVpn);
         systemDefaultCallback.assertNoCallback();
         assertEquals(null, mCm.getActiveNetwork());
 
@@ -8446,14 +8848,14 @@
         final TestNetworkCallback defaultCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultCallback);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
 
-        defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
-                false /* isStrictMode */);
+                false /* privateDnsProbeSent */);
         assertUidRangesUpdatedForMyUid(true);
 
         defaultCallback.assertNoCallback();
@@ -8472,22 +8874,22 @@
         final TestNetworkCallback defaultCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultCallback);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
 
-        defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiAgent);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         mMockVpn.establishForMyUid(true /* validated */, true /* hasInternet */,
-                false /* isStrictMode */);
+                false /* privateDnsProbeSent */);
         assertUidRangesUpdatedForMyUid(true);
 
         defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
         mMockVpn.disconnect();
-        defaultCallback.expect(CallbackEntry.LOST, mMockVpn);
-        defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
+        defaultCallback.expect(LOST, mMockVpn);
+        defaultCallback.expectAvailableCallbacksValidated(mWiFiAgent);
 
         mCm.unregisterNetworkCallback(defaultCallback);
     }
@@ -8498,14 +8900,14 @@
         mCm.registerDefaultNetworkCallback(callback);
 
         // Bring up Ethernet.
-        mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
-        mEthernetNetworkAgent.connect(true);
-        callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
+        mEthernetAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
+        mEthernetAgent.connect(true);
+        callback.expectAvailableThenValidatedCallbacks(mEthernetAgent);
         callback.assertNoCallback();
 
         // Bring up a VPN that has the INTERNET capability, initially unvalidated.
         mMockVpn.establishForMyUid(false /* validated */, true /* hasInternet */,
-                false /* isStrictMode */);
+                false /* privateDnsProbeSent */);
         assertUidRangesUpdatedForMyUid(true);
 
         // Even though the VPN is unvalidated, it becomes the default network for our app.
@@ -8527,16 +8929,16 @@
                 mMockVpn.getAgent().getNetworkCapabilities()));
 
         // Pretend that the VPN network validates.
-        mMockVpn.getAgent().setNetworkValid(false /* isStrictMode */);
+        mMockVpn.getAgent().setNetworkValid(false /* privateDnsProbeSent */);
         mMockVpn.getAgent().mNetworkMonitor.forceReevaluation(Process.myUid());
         // Expect to see the validated capability, but no other changes, because the VPN is already
         // the default network for the app.
-        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mMockVpn);
+        callback.expectCaps(mMockVpn, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
         callback.assertNoCallback();
 
         mMockVpn.disconnect();
-        callback.expect(CallbackEntry.LOST, mMockVpn);
-        callback.expectAvailableCallbacksValidated(mEthernetNetworkAgent);
+        callback.expect(LOST, mMockVpn);
+        callback.expectAvailableCallbacksValidated(mEthernetAgent);
     }
 
     @Test
@@ -8553,18 +8955,18 @@
 
         // Connect cell. It will become the default network, and in the absence of setting
         // underlying networks explicitly it will become the sole underlying network for the vpn.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
+        mCellAgent.connect(true);
 
         mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
-                false /* isStrictMode */);
+                false /* privateDnsProbeSent */);
         assertUidRangesUpdatedForMyUid(true);
 
         vpnNetworkCallback.expectAvailableCallbacks(mMockVpn.getNetwork(),
                 false /* suspended */, false /* validated */, false /* blocked */, TIMEOUT_MS);
-        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn.getNetwork(), TIMEOUT_MS,
-                nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED));
+        vpnNetworkCallback.expectCaps(mMockVpn.getNetwork(), TIMEOUT_MS,
+                c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
 
         final NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
         assertTrue(nc.hasTransport(TRANSPORT_VPN));
@@ -8602,7 +9004,7 @@
         vpnNetworkCallback.assertNoCallback();
 
         mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
-                false /* isStrictMode */);
+                false /* privateDnsProbeSent */);
         assertUidRangesUpdatedForMyUid(true);
 
         vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
@@ -8620,130 +9022,136 @@
         assertDefaultNetworkCapabilities(userId /* no networks */);
 
         // Connect cell and use it as an underlying network.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
+        mCellAgent.connect(true);
+
+        mMockVpn.setUnderlyingNetworks(new Network[] { mCellAgent.getNetwork() });
+
+        vpnNetworkCallback.expectCaps(mMockVpn,
+                c -> c.hasTransport(TRANSPORT_VPN)
+                        && c.hasTransport(TRANSPORT_CELLULAR)
+                        && !c.hasTransport(TRANSPORT_WIFI)
+                        && !c.hasCapability(NET_CAPABILITY_NOT_METERED)
+                        && c.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+        assertDefaultNetworkCapabilities(userId, mCellAgent);
+
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        mWiFiAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
+        mWiFiAgent.connect(true);
 
         mMockVpn.setUnderlyingNetworks(
-                new Network[] { mCellNetworkAgent.getNetwork() });
+                new Network[] { mCellAgent.getNetwork(), mWiFiAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
-                (caps) -> caps.hasTransport(TRANSPORT_VPN)
-                && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
-                && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
-        assertDefaultNetworkCapabilities(userId, mCellNetworkAgent);
-
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
-        mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
-        mWiFiNetworkAgent.connect(true);
-
-        mMockVpn.setUnderlyingNetworks(
-                new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
-
-        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
-                (caps) -> caps.hasTransport(TRANSPORT_VPN)
-                && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
-                && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
-        assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent);
+        vpnNetworkCallback.expectCaps(mMockVpn,
+                c -> c.hasTransport(TRANSPORT_VPN)
+                        && c.hasTransport(TRANSPORT_CELLULAR)
+                        && c.hasTransport(TRANSPORT_WIFI)
+                        && !c.hasCapability(NET_CAPABILITY_NOT_METERED)
+                        && c.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+        assertDefaultNetworkCapabilities(userId, mCellAgent, mWiFiAgent);
 
         // Don't disconnect, but note the VPN is not using wifi any more.
-        mMockVpn.setUnderlyingNetworks(
-                new Network[] { mCellNetworkAgent.getNetwork() });
+        mMockVpn.setUnderlyingNetworks(new Network[] { mCellAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
-                (caps) -> caps.hasTransport(TRANSPORT_VPN)
-                && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
-                && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+        vpnNetworkCallback.expectCaps(mMockVpn,
+                c -> c.hasTransport(TRANSPORT_VPN)
+                        && c.hasTransport(TRANSPORT_CELLULAR)
+                        && !c.hasTransport(TRANSPORT_WIFI)
+                        && !c.hasCapability(NET_CAPABILITY_NOT_METERED)
+                        && c.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
         // The return value of getDefaultNetworkCapabilitiesForUser always includes the default
         // network (wifi) as well as the underlying networks (cell).
-        assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent);
+        assertDefaultNetworkCapabilities(userId, mCellAgent, mWiFiAgent);
 
         // Remove NOT_SUSPENDED from the only network and observe VPN is now suspended.
-        mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_SUSPENDED);
-        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
-                (caps) -> caps.hasTransport(TRANSPORT_VPN)
-                && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
-        vpnNetworkCallback.expect(CallbackEntry.SUSPENDED, mMockVpn);
+        mCellAgent.removeCapability(NET_CAPABILITY_NOT_SUSPENDED);
+        vpnNetworkCallback.expectCaps(mMockVpn,
+                c -> c.hasTransport(TRANSPORT_VPN)
+                        && c.hasTransport(TRANSPORT_CELLULAR)
+                        && !c.hasTransport(TRANSPORT_WIFI)
+                        && !c.hasCapability(NET_CAPABILITY_NOT_METERED)
+                        && !c.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+        vpnNetworkCallback.expect(SUSPENDED, mMockVpn);
 
         // Add NOT_SUSPENDED again and observe VPN is no longer suspended.
-        mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
-        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
-                (caps) -> caps.hasTransport(TRANSPORT_VPN)
-                && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
-                && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
-        vpnNetworkCallback.expect(CallbackEntry.RESUMED, mMockVpn);
+        mCellAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
+        vpnNetworkCallback.expectCaps(mMockVpn,
+                c -> c.hasTransport(TRANSPORT_VPN)
+                        && c.hasTransport(TRANSPORT_CELLULAR)
+                        && !c.hasTransport(TRANSPORT_WIFI)
+                        && !c.hasCapability(NET_CAPABILITY_NOT_METERED)
+                        && c.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+        vpnNetworkCallback.expect(RESUMED, mMockVpn);
 
         // Use Wifi but not cell. Note the VPN is now unmetered and not suspended.
-        mMockVpn.setUnderlyingNetworks(
-                new Network[] { mWiFiNetworkAgent.getNetwork() });
+        mMockVpn.setUnderlyingNetworks(new Network[] { mWiFiAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
-                (caps) -> caps.hasTransport(TRANSPORT_VPN)
-                && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
-                && caps.hasCapability(NET_CAPABILITY_NOT_METERED)
-                && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
-        assertDefaultNetworkCapabilities(userId, mWiFiNetworkAgent);
+        vpnNetworkCallback.expectCaps(mMockVpn,
+                c -> c.hasTransport(TRANSPORT_VPN)
+                        && !c.hasTransport(TRANSPORT_CELLULAR)
+                        && c.hasTransport(TRANSPORT_WIFI)
+                        && c.hasCapability(NET_CAPABILITY_NOT_METERED)
+                        && c.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+        assertDefaultNetworkCapabilities(userId, mWiFiAgent);
 
         // Use both again.
         mMockVpn.setUnderlyingNetworks(
-                new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
+                new Network[] { mCellAgent.getNetwork(), mWiFiAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
-                (caps) -> caps.hasTransport(TRANSPORT_VPN)
-                && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
-                && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
-        assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent);
+        vpnNetworkCallback.expectCaps(mMockVpn,
+                c -> c.hasTransport(TRANSPORT_VPN)
+                        && c.hasTransport(TRANSPORT_CELLULAR)
+                        && c.hasTransport(TRANSPORT_WIFI)
+                        && !c.hasCapability(NET_CAPABILITY_NOT_METERED)
+                        && c.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+        assertDefaultNetworkCapabilities(userId, mCellAgent, mWiFiAgent);
 
         // Cell is suspended again. As WiFi is not, this should not cause a callback.
-        mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_SUSPENDED);
+        mCellAgent.removeCapability(NET_CAPABILITY_NOT_SUSPENDED);
         vpnNetworkCallback.assertNoCallback();
 
         // Stop using WiFi. The VPN is suspended again.
-        mMockVpn.setUnderlyingNetworks(
-                new Network[] { mCellNetworkAgent.getNetwork() });
-        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
-                (caps) -> caps.hasTransport(TRANSPORT_VPN)
-                && caps.hasTransport(TRANSPORT_CELLULAR)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
-        vpnNetworkCallback.expect(CallbackEntry.SUSPENDED, mMockVpn);
-        assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent);
+        mMockVpn.setUnderlyingNetworks(new Network[] { mCellAgent.getNetwork() });
+        vpnNetworkCallback.expectCaps(mMockVpn,
+                c -> c.hasTransport(TRANSPORT_VPN)
+                        && c.hasTransport(TRANSPORT_CELLULAR)
+                        && !c.hasCapability(NET_CAPABILITY_NOT_METERED)
+                        && !c.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+        vpnNetworkCallback.expect(SUSPENDED, mMockVpn);
+        assertDefaultNetworkCapabilities(userId, mCellAgent, mWiFiAgent);
 
         // Use both again.
         mMockVpn.setUnderlyingNetworks(
-                new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
+                new Network[] { mCellAgent.getNetwork(), mWiFiAgent.getNetwork() });
 
-        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
-                (caps) -> caps.hasTransport(TRANSPORT_VPN)
-                && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
-                && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
-        vpnNetworkCallback.expect(CallbackEntry.RESUMED, mMockVpn);
-        assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent);
+        vpnNetworkCallback.expectCaps(mMockVpn,
+                c -> c.hasTransport(TRANSPORT_VPN)
+                        && c.hasTransport(TRANSPORT_CELLULAR)
+                        && c.hasTransport(TRANSPORT_WIFI)
+                        && !c.hasCapability(NET_CAPABILITY_NOT_METERED)
+                        && c.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+        vpnNetworkCallback.expect(RESUMED, mMockVpn);
+        assertDefaultNetworkCapabilities(userId, mCellAgent, mWiFiAgent);
 
         // Disconnect cell. Receive update without even removing the dead network from the
         // underlying networks – it's dead anyway. Not metered any more.
-        mCellNetworkAgent.disconnect();
-        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
-                (caps) -> caps.hasTransport(TRANSPORT_VPN)
-                && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
-                && caps.hasCapability(NET_CAPABILITY_NOT_METERED));
-        assertDefaultNetworkCapabilities(userId, mWiFiNetworkAgent);
+        mCellAgent.disconnect();
+        vpnNetworkCallback.expectCaps(mMockVpn,
+                c -> c.hasTransport(TRANSPORT_VPN)
+                        && !c.hasTransport(TRANSPORT_CELLULAR)
+                        && c.hasTransport(TRANSPORT_WIFI)
+                        && c.hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertDefaultNetworkCapabilities(userId, mWiFiAgent);
 
         // Disconnect wifi too. No underlying networks means this is now metered.
-        mWiFiNetworkAgent.disconnect();
-        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
-                (caps) -> caps.hasTransport(TRANSPORT_VPN)
-                && !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
+        mWiFiAgent.disconnect();
+        vpnNetworkCallback.expectCaps(mMockVpn,
+                c -> c.hasTransport(TRANSPORT_VPN)
+                        && !c.hasTransport(TRANSPORT_CELLULAR)
+                        && !c.hasTransport(TRANSPORT_WIFI)
+                        && !c.hasCapability(NET_CAPABILITY_NOT_METERED));
         // When a network disconnects, the callbacks are fired before all state is updated, so for a
         // short time, synchronous calls will behave as if the network is still connected. Wait for
         // things to settle.
@@ -8767,7 +9175,7 @@
         vpnNetworkCallback.assertNoCallback();
 
         mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */,
-                false /* isStrictMode */);
+                false /* privateDnsProbeSent */);
         assertUidRangesUpdatedForMyUid(true);
 
         vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
@@ -8781,35 +9189,38 @@
         assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
 
         // Connect to Cell; Cell is the default network.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
 
-        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
-                (caps) -> caps.hasTransport(TRANSPORT_VPN)
-                && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
+        vpnNetworkCallback.expectCaps(mMockVpn,
+                c -> c.hasTransport(TRANSPORT_VPN)
+                        && c.hasTransport(TRANSPORT_CELLULAR)
+                        && !c.hasTransport(TRANSPORT_WIFI)
+                        && !c.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Connect to WiFi; WiFi is the new default.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        mWiFiAgent.connect(true);
 
-        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
-                (caps) -> caps.hasTransport(TRANSPORT_VPN)
-                && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
-                && caps.hasCapability(NET_CAPABILITY_NOT_METERED));
+        vpnNetworkCallback.expectCaps(mMockVpn,
+                c -> c.hasTransport(TRANSPORT_VPN)
+                        && !c.hasTransport(TRANSPORT_CELLULAR)
+                        && c.hasTransport(TRANSPORT_WIFI)
+                        && c.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Disconnect Cell. The default network did not change, so there shouldn't be any changes in
         // the capabilities.
-        mCellNetworkAgent.disconnect();
+        mCellAgent.disconnect();
 
         // Disconnect wifi too. Now we have no default network.
-        mWiFiNetworkAgent.disconnect();
+        mWiFiAgent.disconnect();
 
-        vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
-                (caps) -> caps.hasTransport(TRANSPORT_VPN)
-                && !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
-                && !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
+        vpnNetworkCallback.expectCaps(mMockVpn,
+                c -> c.hasTransport(TRANSPORT_VPN)
+                        && !c.hasTransport(TRANSPORT_CELLULAR)
+                        && !c.hasTransport(TRANSPORT_WIFI)
+                        && !c.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         mMockVpn.disconnect();
     }
@@ -8825,6 +9236,14 @@
         final TestNetworkCallback callback = new TestNetworkCallback();
         mCm.registerNetworkCallback(request, callback);
 
+        // File a VPN request to prevent VPN network being lingered.
+        final NetworkRequest vpnRequest = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_VPN)
+                .removeCapability(NET_CAPABILITY_NOT_VPN)
+                .build();
+        final TestNetworkCallback vpnCallback = new TestNetworkCallback();
+        mCm.requestNetwork(vpnRequest, vpnCallback);
+
         // Bring up a VPN
         mMockVpn.establishForMyUid();
         assertUidRangesUpdatedForMyUid(true);
@@ -8838,14 +9257,12 @@
         assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE);
 
         // Set an underlying network and expect to see the VPN transports change.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expectCapabilitiesThat(mMockVpn, (caps)
-                -> caps.hasTransport(TRANSPORT_VPN)
-                && caps.hasTransport(TRANSPORT_WIFI));
-        callback.expectCapabilitiesThat(mWiFiNetworkAgent, (caps)
-                -> caps.hasCapability(NET_CAPABILITY_VALIDATED));
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        callback.expectCaps(mMockVpn, c -> c.hasTransport(TRANSPORT_VPN)
+                        && c.hasTransport(TRANSPORT_WIFI));
+        callback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
 
         doReturn(UserHandle.getUid(RESTRICTED_USER, VPN_UID)).when(mPackageManager)
                 .getPackageUidAsUser(ALWAYS_ON_PACKAGE, RESTRICTED_USER);
@@ -8856,35 +9273,38 @@
         // Expect that the VPN UID ranges contain both |uid| and the UID range for the newly-added
         // restricted user.
         final UidRange rRange = UidRange.createForUser(UserHandle.of(RESTRICTED_USER));
-        final Range<Integer> restrictUidRange = new Range<Integer>(rRange.start, rRange.stop);
-        final Range<Integer> singleUidRange = new Range<Integer>(uid, uid);
-        callback.expectCapabilitiesThat(mMockVpn, (caps)
-                -> caps.getUids().size() == 2
-                && caps.getUids().contains(singleUidRange)
-                && caps.getUids().contains(restrictUidRange)
-                && caps.hasTransport(TRANSPORT_VPN)
-                && caps.hasTransport(TRANSPORT_WIFI));
+        final Range<Integer> restrictUidRange = new Range<>(rRange.start, rRange.stop);
+        final Range<Integer> singleUidRange = new Range<>(uid, uid);
+        callback.expectCaps(mMockVpn, c ->
+                c.getUids().size() == 2
+                && c.getUids().contains(singleUidRange)
+                && c.getUids().contains(restrictUidRange)
+                && c.hasTransport(TRANSPORT_VPN)
+                && c.hasTransport(TRANSPORT_WIFI));
 
         // Change the VPN's capabilities somehow (specifically, disconnect wifi).
-        mWiFiNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        callback.expectCapabilitiesThat(mMockVpn, (caps)
-                -> caps.getUids().size() == 2
-                && caps.getUids().contains(singleUidRange)
-                && caps.getUids().contains(restrictUidRange)
-                && caps.hasTransport(TRANSPORT_VPN)
-                && !caps.hasTransport(TRANSPORT_WIFI));
+        mWiFiAgent.disconnect();
+        callback.expect(LOST, mWiFiAgent);
+        callback.expectCaps(mMockVpn, c ->
+                c.getUids().size() == 2
+                && c.getUids().contains(singleUidRange)
+                && c.getUids().contains(restrictUidRange)
+                && c.hasTransport(TRANSPORT_VPN)
+                && !c.hasTransport(TRANSPORT_WIFI));
 
         // User removed and expect to lose the UID range for the restricted user.
         mMockVpn.onUserRemoved(RESTRICTED_USER);
 
         // Expect that the VPN gains the UID range for the restricted user, and that the capability
         // change made just before that (i.e., loss of TRANSPORT_WIFI) is preserved.
-        callback.expectCapabilitiesThat(mMockVpn, (caps)
-                -> caps.getUids().size() == 1
-                && caps.getUids().contains(singleUidRange)
-                && caps.hasTransport(TRANSPORT_VPN)
-                && !caps.hasTransport(TRANSPORT_WIFI));
+        callback.expectCaps(mMockVpn, c ->
+                c.getUids().size() == 1
+                && c.getUids().contains(singleUidRange)
+                && c.hasTransport(TRANSPORT_VPN)
+                && !c.hasTransport(TRANSPORT_WIFI));
+
+        mCm.unregisterNetworkCallback(callback);
+        mCm.unregisterNetworkCallback(vpnCallback);
     }
 
     @Test
@@ -8907,8 +9327,8 @@
         final int uid = Process.myUid();
 
         // Connect wifi and check that UIDs in the main and restricted profiles have network access.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true /* validated */);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true /* validated */);
         final int restrictedUid = UserHandle.getUid(RESTRICTED_USER, 42 /* appId */);
         assertNotNull(mCm.getActiveNetworkForUid(uid));
         assertNotNull(mCm.getActiveNetworkForUid(restrictedUid));
@@ -8957,9 +9377,9 @@
     public void testIsActiveNetworkMeteredOverWifi() throws Exception {
         // Returns true by default when no network is available.
         assertTrue(mCm.isActiveNetworkMetered());
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        mWiFiAgent.connect(true);
         waitForIdle();
 
         assertFalse(mCm.isActiveNetworkMetered());
@@ -8969,9 +9389,9 @@
     public void testIsActiveNetworkMeteredOverCell() throws Exception {
         // Returns true by default when no network is available.
         assertTrue(mCm.isActiveNetworkMetered());
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+        mCellAgent.connect(true);
         waitForIdle();
 
         assertTrue(mCm.isActiveNetworkMetered());
@@ -8981,9 +9401,9 @@
     public void testIsActiveNetworkMeteredOverVpnTrackingPlatformDefault() throws Exception {
         // Returns true by default when no network is available.
         assertTrue(mCm.isActiveNetworkMetered());
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+        mCellAgent.connect(true);
         waitForIdle();
         assertTrue(mCm.isActiveNetworkMetered());
 
@@ -8998,9 +9418,9 @@
         assertTrue(mCm.isActiveNetworkMetered());
 
         // Connect WiFi.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        mWiFiAgent.connect(true);
         waitForIdle();
         // VPN should still be the active network.
         assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
@@ -9009,13 +9429,13 @@
         assertFalse(mCm.isActiveNetworkMetered());
 
         // Disconnecting Cell should not affect VPN's meteredness.
-        mCellNetworkAgent.disconnect();
+        mCellAgent.disconnect();
         waitForIdle();
 
         assertFalse(mCm.isActiveNetworkMetered());
 
         // Disconnect WiFi; Now there is no platform default network.
-        mWiFiNetworkAgent.disconnect();
+        mWiFiAgent.disconnect();
         waitForIdle();
 
         // VPN without any underlying networks is treated as metered.
@@ -9028,15 +9448,15 @@
     public void testIsActiveNetworkMeteredOverVpnSpecifyingUnderlyingNetworks() throws Exception {
         // Returns true by default when no network is available.
         assertTrue(mCm.isActiveNetworkMetered());
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+        mCellAgent.connect(true);
         waitForIdle();
         assertTrue(mCm.isActiveNetworkMetered());
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        mWiFiAgent.connect(true);
         waitForIdle();
         assertFalse(mCm.isActiveNetworkMetered());
 
@@ -9047,16 +9467,14 @@
         // Ensure VPN is now the active network.
         assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
         // VPN is using Cell
-        mMockVpn.setUnderlyingNetworks(
-                new Network[] { mCellNetworkAgent.getNetwork() });
+        mMockVpn.setUnderlyingNetworks(new Network[] { mCellAgent.getNetwork() });
         waitForIdle();
 
         // Expect VPN to be metered.
         assertTrue(mCm.isActiveNetworkMetered());
 
         // VPN is now using WiFi
-        mMockVpn.setUnderlyingNetworks(
-                new Network[] { mWiFiNetworkAgent.getNetwork() });
+        mMockVpn.setUnderlyingNetworks(new Network[] { mWiFiAgent.getNetwork() });
         waitForIdle();
 
         // Expect VPN to be unmetered
@@ -9064,7 +9482,7 @@
 
         // VPN is using Cell | WiFi.
         mMockVpn.setUnderlyingNetworks(
-                new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
+                new Network[] { mCellAgent.getNetwork(), mWiFiAgent.getNetwork() });
         waitForIdle();
 
         // Expect VPN to be metered.
@@ -9072,7 +9490,7 @@
 
         // VPN is using WiFi | Cell.
         mMockVpn.setUnderlyingNetworks(
-                new Network[] { mWiFiNetworkAgent.getNetwork(), mCellNetworkAgent.getNetwork() });
+                new Network[] { mWiFiAgent.getNetwork(), mCellAgent.getNetwork() });
         waitForIdle();
 
         // Order should not matter and VPN should still be metered.
@@ -9092,9 +9510,9 @@
     public void testIsActiveNetworkMeteredOverAlwaysMeteredVpn() throws Exception {
         // Returns true by default when no network is available.
         assertTrue(mCm.isActiveNetworkMetered());
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        mWiFiAgent.connect(true);
         waitForIdle();
         assertFalse(mCm.isActiveNetworkMetered());
 
@@ -9114,8 +9532,7 @@
 
 
         // VPN explicitly declares WiFi as its underlying network.
-        mMockVpn.setUnderlyingNetworks(
-                new Network[] { mWiFiNetworkAgent.getNetwork() });
+        mMockVpn.setUnderlyingNetworks(new Network[] { mWiFiAgent.getNetwork() });
         waitForIdle();
 
         // Doesn't really matter whether VPN declares its underlying networks explicitly.
@@ -9123,7 +9540,7 @@
 
         // With WiFi lost, VPN is basically without any underlying networks. And in that case it is
         // anyways suppose to be metered.
-        mWiFiNetworkAgent.disconnect();
+        mWiFiAgent.disconnect();
         waitForIdle();
 
         assertTrue(mCm.isActiveNetworkMetered());
@@ -9135,11 +9552,6 @@
         public void expectAvailableThenValidatedCallbacks(HasNetwork n, int blockedStatus) {
             super.expectAvailableThenValidatedCallbacks(n.getNetwork(), blockedStatus, TIMEOUT_MS);
         }
-        public void expectBlockedStatusCallback(HasNetwork n, int blockedStatus) {
-            // This doesn't work:
-            // super.expectBlockedStatusCallback(blockedStatus, n.getNetwork());
-            super.expectBlockedStatusCallback(blockedStatus, n.getNetwork(), TIMEOUT_MS);
-        }
         public void onBlockedStatusChanged(Network network, int blockedReasons) {
             getHistory().add(new CallbackEntry.BlockedStatusInt(network, blockedReasons));
         }
@@ -9157,80 +9569,83 @@
 
         mockUidNetworkingBlocked();
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        detailedCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent,
-                BLOCKED_REASON_NONE);
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        detailedCallback.expectAvailableThenValidatedCallbacks(mCellAgent, BLOCKED_REASON_NONE);
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
-        assertExtraInfoFromCmPresent(mCellNetworkAgent);
+        assertExtraInfoFromCmPresent(mCellAgent);
 
         setBlockedReasonChanged(BLOCKED_REASON_BATTERY_SAVER);
-        cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
-        detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent,
-                BLOCKED_REASON_BATTERY_SAVER);
+        cellNetworkCallback.expect(BLOCKED_STATUS, mCellAgent, cb -> cb.getBlocked());
+        detailedCallback.expect(BLOCKED_STATUS_INT, mCellAgent,
+                cb -> cb.getReason() == BLOCKED_REASON_BATTERY_SAVER);
         assertNull(mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
-        assertExtraInfoFromCmBlocked(mCellNetworkAgent);
+        assertExtraInfoFromCmBlocked(mCellAgent);
 
         // If blocked state does not change but blocked reason does, the boolean callback is called.
         // TODO: investigate de-duplicating.
         setBlockedReasonChanged(BLOCKED_METERED_REASON_USER_RESTRICTED);
-        cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
-        detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent,
-                BLOCKED_METERED_REASON_USER_RESTRICTED);
+        cellNetworkCallback.expect(BLOCKED_STATUS, mCellAgent, cb -> cb.getBlocked());
+        detailedCallback.expect(BLOCKED_STATUS_INT, mCellAgent,
+                cb -> cb.getReason() == BLOCKED_METERED_REASON_USER_RESTRICTED);
 
         setBlockedReasonChanged(BLOCKED_REASON_NONE);
-        cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
-        detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, BLOCKED_REASON_NONE);
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        cellNetworkCallback.expect(BLOCKED_STATUS, mCellAgent, cb -> !cb.getBlocked());
+        detailedCallback.expect(BLOCKED_STATUS_INT, mCellAgent,
+                cb -> cb.getReason() == BLOCKED_REASON_NONE);
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
-        assertExtraInfoFromCmPresent(mCellNetworkAgent);
+        assertExtraInfoFromCmPresent(mCellAgent);
 
         setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER);
-        cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
-        detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent,
-                BLOCKED_METERED_REASON_DATA_SAVER);
+        cellNetworkCallback.expect(BLOCKED_STATUS, mCellAgent, cb -> cb.getBlocked());
+        detailedCallback.expect(BLOCKED_STATUS_INT, mCellAgent,
+                cb -> cb.getReason() == BLOCKED_METERED_REASON_DATA_SAVER);
         assertNull(mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
-        assertExtraInfoFromCmBlocked(mCellNetworkAgent);
+        assertExtraInfoFromCmBlocked(mCellAgent);
 
         // Restrict the network based on UID rule and NOT_METERED capability change.
-        mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
-        cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent);
-        cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
-        detailedCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent);
-        detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, BLOCKED_REASON_NONE);
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        mCellAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        cellNetworkCallback.expectCaps(mCellAgent,
+                c -> c.hasCapability(NET_CAPABILITY_NOT_METERED));
+        cellNetworkCallback.expect(BLOCKED_STATUS, mCellAgent, cb -> !cb.getBlocked());
+        detailedCallback.expectCaps(mCellAgent, c -> c.hasCapability(NET_CAPABILITY_NOT_METERED));
+        detailedCallback.expect(BLOCKED_STATUS_INT, mCellAgent,
+                cb -> cb.getReason() == BLOCKED_REASON_NONE);
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
-        assertExtraInfoFromCmPresent(mCellNetworkAgent);
+        assertExtraInfoFromCmPresent(mCellAgent);
 
-        mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
-        cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED,
-                mCellNetworkAgent);
-        cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
-        detailedCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED,
-                mCellNetworkAgent);
-        detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent,
-                BLOCKED_METERED_REASON_DATA_SAVER);
+        mCellAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+        cellNetworkCallback.expectCaps(mCellAgent,
+                c -> !c.hasCapability(NET_CAPABILITY_NOT_METERED));
+        cellNetworkCallback.expect(BLOCKED_STATUS, mCellAgent, cb -> cb.getBlocked());
+        detailedCallback.expectCaps(mCellAgent,
+                c -> !c.hasCapability(NET_CAPABILITY_NOT_METERED));
+        detailedCallback.expect(BLOCKED_STATUS_INT, mCellAgent,
+                cb -> cb.getReason() == BLOCKED_METERED_REASON_DATA_SAVER);
         assertNull(mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
-        assertExtraInfoFromCmBlocked(mCellNetworkAgent);
+        assertExtraInfoFromCmBlocked(mCellAgent);
 
         setBlockedReasonChanged(BLOCKED_REASON_NONE);
-        cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
-        detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, BLOCKED_REASON_NONE);
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        cellNetworkCallback.expect(BLOCKED_STATUS, mCellAgent, cb -> !cb.getBlocked());
+        detailedCallback.expect(BLOCKED_STATUS_INT, mCellAgent,
+                cb -> cb.getReason() == BLOCKED_REASON_NONE);
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
-        assertExtraInfoFromCmPresent(mCellNetworkAgent);
+        assertExtraInfoFromCmPresent(mCellAgent);
 
         setBlockedReasonChanged(BLOCKED_REASON_NONE);
         cellNetworkCallback.assertNoCallback();
@@ -9238,30 +9653,31 @@
 
         // Restrict background data. Networking is not blocked because the network is unmetered.
         setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER);
-        cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
-        detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent,
-                BLOCKED_METERED_REASON_DATA_SAVER);
+        cellNetworkCallback.expect(BLOCKED_STATUS, mCellAgent, cb -> cb.getBlocked());
+        detailedCallback.expect(BLOCKED_STATUS_INT, mCellAgent,
+                cb -> cb.getReason() == BLOCKED_METERED_REASON_DATA_SAVER);
         assertNull(mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
-        assertExtraInfoFromCmBlocked(mCellNetworkAgent);
+        assertExtraInfoFromCmBlocked(mCellAgent);
         setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER);
         cellNetworkCallback.assertNoCallback();
 
         setBlockedReasonChanged(BLOCKED_REASON_NONE);
-        cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
-        detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, BLOCKED_REASON_NONE);
+        cellNetworkCallback.expect(BLOCKED_STATUS, mCellAgent, cb -> !cb.getBlocked());
+        detailedCallback.expect(BLOCKED_STATUS_INT, mCellAgent,
+                cb -> cb.getReason() == BLOCKED_REASON_NONE);
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
-        assertExtraInfoFromCmPresent(mCellNetworkAgent);
+        assertExtraInfoFromCmPresent(mCellAgent);
 
         setBlockedReasonChanged(BLOCKED_REASON_NONE);
         cellNetworkCallback.assertNoCallback();
         detailedCallback.assertNoCallback();
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
-        assertExtraInfoFromCmPresent(mCellNetworkAgent);
+        assertExtraInfoFromCmPresent(mCellAgent);
 
         mCm.unregisterNetworkCallback(cellNetworkCallback);
     }
@@ -9278,34 +9694,34 @@
         setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER);
         defaultCallback.assertNoCallback();
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
-        defaultCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellAgent);
+        defaultCallback.expectCaps(mCellAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
 
         // Allow to use the network after switching to NOT_METERED network.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
-        mWiFiNetworkAgent.connect(true);
-        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        mWiFiAgent.connect(true);
+        defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
 
         // Switch to METERED network. Restrict the use of the network.
-        mWiFiNetworkAgent.disconnect();
-        defaultCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacksValidatedAndBlocked(mCellNetworkAgent);
+        mWiFiAgent.disconnect();
+        defaultCallback.expect(LOST, mWiFiAgent);
+        defaultCallback.expectAvailableCallbacksValidatedAndBlocked(mCellAgent);
 
         // Network becomes NOT_METERED.
-        mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
-        defaultCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent);
-        defaultCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
+        mCellAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        defaultCallback.expectCaps(mCellAgent, c -> c.hasCapability(NET_CAPABILITY_NOT_METERED));
+        defaultCallback.expect(BLOCKED_STATUS, mCellAgent, cb -> !cb.getBlocked());
 
         // Verify there's no Networkcallbacks invoked after data saver on/off.
         setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER);
         setBlockedReasonChanged(BLOCKED_REASON_NONE);
         defaultCallback.assertNoCallback();
 
-        mCellNetworkAgent.disconnect();
-        defaultCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        mCellAgent.disconnect();
+        defaultCallback.expect(LOST, mCellAgent);
         defaultCallback.assertNoCallback();
 
         mCm.unregisterNetworkCallback(defaultCallback);
@@ -9369,7 +9785,7 @@
 
         // Expect exactly one blocked callback for each agent.
         for (int i = 0; i < agents.length; i++) {
-            final CallbackEntry e = callback.expect(CallbackEntry.BLOCKED_STATUS, TIMEOUT_MS,
+            final CallbackEntry e = callback.expect(BLOCKED_STATUS, TIMEOUT_MS,
                     c -> c.getBlocked() == blocked);
             final Network network = e.getNetwork();
             assertTrue("Received unexpected blocked callback for network " + network,
@@ -9412,7 +9828,7 @@
 
         final Set<Integer> excludedUids = new ArraySet<Integer>();
         excludedUids.add(VPN_UID);
-        if (SdkLevel.isAtLeastT()) {
+        if (mDeps.isAtLeastT()) {
             // On T onwards, the corresponding SDK sandbox UID should also be excluded
             excludedUids.add(toSdkSandboxUid(VPN_UID));
         }
@@ -9422,14 +9838,14 @@
         expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcels);
 
         // Connect a network when lockdown is active, expect to see it blocked.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(false /* validated */);
-        callback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
-        defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
-        vpnUidCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        vpnUidDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        vpnDefaultCallbackAsUid.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false /* validated */);
+        callback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiAgent);
+        vpnUidCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        vpnUidDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        vpnDefaultCallbackAsUid.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertNull(mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
         // Mobile is BLOCKED even though it's not actually connected.
@@ -9438,14 +9854,14 @@
 
         // Disable lockdown, expect to see the network unblocked.
         mMockVpn.setAlwaysOnPackage(null, false /* lockdown */, allowList);
-        callback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
-        defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
+        callback.expect(BLOCKED_STATUS, mWiFiAgent, cb -> !cb.getBlocked());
+        defaultCallback.expect(BLOCKED_STATUS, mWiFiAgent, cb -> !cb.getBlocked());
         vpnUidCallback.assertNoCallback();
         vpnUidDefaultCallback.assertNoCallback();
         vpnDefaultCallbackAsUid.assertNoCallback();
         expectNetworkRejectNonSecureVpn(inOrder, false, uidRangeParcels);
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -9460,29 +9876,29 @@
         vpnDefaultCallbackAsUid.assertNoCallback();
 
         excludedUids.add(uid);
-        if (SdkLevel.isAtLeastT()) {
+        if (mDeps.isAtLeastT()) {
             // On T onwards, the corresponding SDK sandbox UID should also be excluded
             excludedUids.add(toSdkSandboxUid(uid));
         }
         final UidRangeParcel[] uidRangeParcelsAlsoExcludingUs = uidRangeParcelsExcludingUids(
                 excludedUids.toArray(new Integer[0]));
         expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcelsAlsoExcludingUs);
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
 
         // Connect a new network, expect it to be unblocked.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(false /* validated */);
-        callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(false /* validated */);
+        callback.expectAvailableCallbacksUnvalidated(mCellAgent);
         defaultCallback.assertNoCallback();
-        vpnUidCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        vpnUidCallback.expectAvailableCallbacksUnvalidated(mCellAgent);
         vpnUidDefaultCallback.assertNoCallback();
         vpnDefaultCallbackAsUid.assertNoCallback();
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
         // Cellular is DISCONNECTED because it's not the default and there are no requests for it.
         assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
@@ -9497,12 +9913,12 @@
         mMockVpn.setAlwaysOnPackage(ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
         waitForIdle();
         expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcels);
-        defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
-        assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
+        defaultCallback.expect(BLOCKED_STATUS, mWiFiAgent, cb -> cb.getBlocked());
+        assertBlockedCallbackInAnyOrder(callback, true, mWiFiAgent, mCellAgent);
         vpnUidCallback.assertNoCallback();
         vpnUidDefaultCallback.assertNoCallback();
         vpnDefaultCallbackAsUid.assertNoCallback();
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertNull(mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
@@ -9510,13 +9926,13 @@
 
         // Disable lockdown. Everything is unblocked.
         mMockVpn.setAlwaysOnPackage(null, false /* lockdown */, allowList);
-        defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
-        assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent);
+        defaultCallback.expect(BLOCKED_STATUS, mWiFiAgent, cb -> !cb.getBlocked());
+        assertBlockedCallbackInAnyOrder(callback, false, mWiFiAgent, mCellAgent);
         vpnUidCallback.assertNoCallback();
         vpnUidDefaultCallback.assertNoCallback();
         vpnDefaultCallbackAsUid.assertNoCallback();
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -9530,8 +9946,8 @@
         vpnUidCallback.assertNoCallback();
         vpnUidDefaultCallback.assertNoCallback();
         vpnDefaultCallbackAsUid.assertNoCallback();
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -9543,20 +9959,20 @@
         vpnUidCallback.assertNoCallback();
         vpnUidDefaultCallback.assertNoCallback();
         vpnDefaultCallbackAsUid.assertNoCallback();
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
 
         // Enable lockdown and connect a VPN. The VPN is not blocked.
         mMockVpn.setAlwaysOnPackage(ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
-        defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
-        assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
+        defaultCallback.expect(BLOCKED_STATUS, mWiFiAgent, cb -> cb.getBlocked());
+        assertBlockedCallbackInAnyOrder(callback, true, mWiFiAgent, mCellAgent);
         vpnUidCallback.assertNoCallback();
         vpnUidDefaultCallback.assertNoCallback();
         vpnDefaultCallbackAsUid.assertNoCallback();
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertNull(mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
@@ -9569,15 +9985,15 @@
         vpnUidDefaultCallback.assertNoCallback();  // VPN does not apply to VPN_UID
         vpnDefaultCallbackAsUid.assertNoCallback();
         assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
         assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
 
         mMockVpn.disconnect();
-        defaultCallback.expect(CallbackEntry.LOST, mMockVpn);
-        defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
+        defaultCallback.expect(LOST, mMockVpn);
+        defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiAgent);
         vpnUidCallback.assertNoCallback();
         vpnUidDefaultCallback.assertNoCallback();
         vpnDefaultCallbackAsUid.assertNoCallback();
@@ -9596,8 +10012,8 @@
         mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
 
         // Connect Wi-Fi.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true /* validated */);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true /* validated */);
 
         // Connect a VPN that excludes its UID from its UID ranges.
         final LinkProperties lp = new LinkProperties();
@@ -9606,7 +10022,7 @@
         final Set<UidRange> ranges = new ArraySet<>();
         ranges.add(new UidRange(0, myUid - 1));
         ranges.add(new UidRange(myUid + 1, UserHandle.PER_USER_RANGE - 1));
-        mMockVpn.setUnderlyingNetworks(new Network[]{mWiFiNetworkAgent.getNetwork()});
+        mMockVpn.setUnderlyingNetworks(new Network[] { mWiFiAgent.getNetwork() });
         mMockVpn.establish(lp, myUid, ranges);
 
         // Wait for validation before registering callbacks.
@@ -9628,8 +10044,8 @@
         perUidCb.expectAvailableCallbacksValidated(mMockVpn);
         // getActiveNetwork is not affected by this bug.
         assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetworkForUid(myUid + 1));
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(myUid));
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetworkForUid(myUid));
 
         doAsUid(otherUid, () -> mCm.unregisterNetworkCallback(otherUidCb));
         mCm.unregisterNetworkCallback(defaultCb);
@@ -9712,11 +10128,11 @@
         cellLp.setInterfaceName("rmnet0");
         cellLp.addLinkAddress(new LinkAddress("2001:db8::1/64"));
         cellLp.addRoute(new RouteInfo(new IpPrefix("::/0"), null, "rmnet0"));
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
-        mCellNetworkAgent.connect(false /* validated */);
-        callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
-        defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
-        systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mCellAgent.connect(false /* validated */);
+        callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellAgent);
+        systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellAgent);
         waitForIdle();
         assertNull(mMockVpn.getAgent());
 
@@ -9725,37 +10141,36 @@
         // TODO: consider fixing this.
         cellLp.addLinkAddress(new LinkAddress("192.0.2.2/25"));
         cellLp.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0"), null, "rmnet0"));
-        mCellNetworkAgent.sendLinkProperties(cellLp);
-        callback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
-        defaultCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
-        systemDefaultCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED,
-                mCellNetworkAgent);
+        mCellAgent.sendLinkProperties(cellLp);
+        callback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
+        defaultCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
+        systemDefaultCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
         waitForIdle();
         assertNull(mMockVpn.getAgent());
 
         // Disconnect, then try again with a network that supports IPv4 at connection time.
         // Expect lockdown VPN to come up.
         ExpectedBroadcast b1 = expectConnectivityAction(TYPE_MOBILE, DetailedState.DISCONNECTED);
-        mCellNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mCellNetworkAgent);
-        defaultCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
-        systemDefaultCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        mCellAgent.disconnect();
+        callback.expect(LOST, mCellAgent);
+        defaultCallback.expect(LOST, mCellAgent);
+        systemDefaultCallback.expect(LOST, mCellAgent);
         b1.expectBroadcast();
 
         // When lockdown VPN is active, the NetworkInfo state in CONNECTIVITY_ACTION is overwritten
         // with the state of the VPN network. So expect a CONNECTING broadcast.
         b1 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTING);
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
-        mCellNetworkAgent.connect(false /* validated */);
-        callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
-        defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
-        systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mCellAgent.connect(false /* validated */);
+        callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellAgent);
+        defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellAgent);
+        systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellAgent);
         b1.expectBroadcast();
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_VPN, DetailedState.BLOCKED);
-        assertExtraInfoFromCmBlocked(mCellNetworkAgent);
+        assertExtraInfoFromCmBlocked(mCellAgent);
 
         // TODO: it would be nice if we could simply rely on the production code here, and have
         // LockdownVpnTracker start the VPN, have the VPN code register its NetworkAgent with
@@ -9773,7 +10188,7 @@
         mMockVpn.expectStartLegacyVpnRunner();
         b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED);
         ExpectedBroadcast b2 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED);
-        establishLegacyLockdownVpn(mCellNetworkAgent.getNetwork());
+        establishLegacyLockdownVpn(mCellAgent.getNetwork());
         callback.expectAvailableThenValidatedCallbacks(mMockVpn);
         defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
         systemDefaultCallback.assertNoCallback();
@@ -9784,7 +10199,7 @@
         assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED);
         assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED);
-        assertExtraInfoFromCmPresent(mCellNetworkAgent);
+        assertExtraInfoFromCmPresent(mCellAgent);
         assertTrue(vpnNc.hasTransport(TRANSPORT_VPN));
         assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR));
         assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI));
@@ -9799,13 +10214,13 @@
         final NetworkCapabilities wifiNc = new NetworkCapabilities();
         wifiNc.addTransportType(TRANSPORT_WIFI);
         wifiNc.addCapability(NET_CAPABILITY_NOT_METERED);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp, wifiNc);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp, wifiNc);
 
         b1 = expectConnectivityAction(TYPE_MOBILE, DetailedState.DISCONNECTED);
         // Wifi is CONNECTING because the VPN isn't up yet.
         b2 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTING);
         ExpectedBroadcast b3 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED);
-        mWiFiNetworkAgent.connect(false /* validated */);
+        mWiFiAgent.connect(false /* validated */);
         b1.expectBroadcast();
         b2.expectBroadcast();
         b3.expectBroadcast();
@@ -9816,23 +10231,23 @@
         // connected, so the network is not considered blocked by the lockdown UID ranges? But the
         // fact that a VPN is connected should only result in the VPN itself being unblocked, not
         // any other network. Bug in isUidBlockedByVpn?
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        callback.expect(CallbackEntry.LOST, mMockVpn);
-        defaultCallback.expect(CallbackEntry.LOST, mMockVpn);
-        defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
-        systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        callback.expect(LOST, mMockVpn);
+        defaultCallback.expect(LOST, mMockVpn);
+        defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiAgent);
+        systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
 
         // While the VPN is reconnecting on the new network, everything is blocked.
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
         assertNetworkInfo(TYPE_VPN, DetailedState.BLOCKED);
-        assertExtraInfoFromCmBlocked(mWiFiNetworkAgent);
+        assertExtraInfoFromCmBlocked(mWiFiAgent);
 
         // The VPN comes up again on wifi.
         b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED);
         b2 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED);
-        establishLegacyLockdownVpn(mWiFiNetworkAgent.getNetwork());
+        establishLegacyLockdownVpn(mWiFiAgent.getNetwork());
         callback.expectAvailableThenValidatedCallbacks(mMockVpn);
         defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
         systemDefaultCallback.assertNoCallback();
@@ -9842,7 +10257,7 @@
         assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED);
-        assertExtraInfoFromCmPresent(mWiFiNetworkAgent);
+        assertExtraInfoFromCmPresent(mWiFiAgent);
         vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
         assertTrue(vpnNc.hasTransport(TRANSPORT_VPN));
         assertTrue(vpnNc.hasTransport(TRANSPORT_WIFI));
@@ -9850,8 +10265,8 @@
         assertTrue(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Disconnect cell. Nothing much happens since it's not the default network.
-        mCellNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        mCellAgent.disconnect();
+        callback.expect(LOST, mCellAgent);
         defaultCallback.assertNoCallback();
         systemDefaultCallback.assertNoCallback();
 
@@ -9859,20 +10274,21 @@
         assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED);
-        assertExtraInfoFromCmPresent(mWiFiNetworkAgent);
+        assertExtraInfoFromCmPresent(mWiFiAgent);
 
         b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED);
         b2 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED);
-        mWiFiNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        systemDefaultCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        mWiFiAgent.disconnect();
+        callback.expect(LOST, mWiFiAgent);
+        systemDefaultCallback.expect(LOST, mWiFiAgent);
         b1.expectBroadcast();
-        callback.expectCapabilitiesThat(mMockVpn, nc -> !nc.hasTransport(TRANSPORT_WIFI));
+        callback.expectCaps(mMockVpn, c -> !c.hasTransport(TRANSPORT_WIFI));
         mMockVpn.expectStopVpnRunnerPrivileged();
-        callback.expect(CallbackEntry.LOST, mMockVpn);
+        callback.expect(LOST, mMockVpn);
         b2.expectBroadcast();
 
         VMSHandlerThread.quitSafely();
+        VMSHandlerThread.join();
     }
 
     @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
@@ -9954,6 +10370,50 @@
         }
     }
 
+    private void doTestSetFirewallChainEnabledCloseSocket(final int chain,
+            final boolean isAllowList) throws Exception {
+        reset(mDestroySocketsWrapper);
+
+        mCm.setFirewallChainEnabled(chain, true /* enabled */);
+        final Set<Integer> uids =
+                new ArraySet<>(List.of(TEST_PACKAGE_UID, TEST_PACKAGE_UID2));
+        if (isAllowList) {
+            final Set<Range<Integer>> range = new ArraySet<>(
+                    List.of(new Range<>(Process.FIRST_APPLICATION_UID, Integer.MAX_VALUE)));
+            verify(mDestroySocketsWrapper).destroyLiveTcpSockets(range, uids);
+        } else {
+            verify(mDestroySocketsWrapper).destroyLiveTcpSocketsByOwnerUids(uids);
+        }
+
+        mCm.setFirewallChainEnabled(chain, false /* enabled */);
+        verifyNoMoreInteractions(mDestroySocketsWrapper);
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testSetFirewallChainEnabledCloseSocket() throws Exception {
+        doReturn(new ArraySet<>(Arrays.asList(TEST_PACKAGE_UID, TEST_PACKAGE_UID2)))
+                .when(mBpfNetMaps)
+                .getUidsWithDenyRuleOnDenyListChain(anyInt());
+        doReturn(new ArraySet<>(Arrays.asList(TEST_PACKAGE_UID, TEST_PACKAGE_UID2)))
+                .when(mBpfNetMaps)
+                .getUidsWithAllowRuleOnAllowListChain(anyInt());
+
+        final boolean allowlist = true;
+        final boolean denylist = false;
+
+        doReturn(true).when(mBpfNetMaps).isFirewallAllowList(anyInt());
+        doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_DOZABLE, allowlist);
+        doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_POWERSAVE, allowlist);
+        doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_RESTRICTED, allowlist);
+        doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_LOW_POWER_STANDBY, allowlist);
+
+        doReturn(false).when(mBpfNetMaps).isFirewallAllowList(anyInt());
+        doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_STANDBY, denylist);
+        doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_OEM_DENY_1, denylist);
+        doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_OEM_DENY_2, denylist);
+        doTestSetFirewallChainEnabledCloseSocket(FIREWALL_CHAIN_OEM_DENY_3, denylist);
+    }
+
     private void doTestReplaceFirewallChain(final int chain) {
         final int[] uids = new int[] {1001, 1002};
         mCm.replaceFirewallChain(chain, uids);
@@ -10016,32 +10476,32 @@
                     callbackWithoutCap);
 
             // Setup networks with testing capability and verify the default network changes.
-            mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-            mCellNetworkAgent.addCapability(testCap);
-            mCellNetworkAgent.connect(true);
-            callbackWithCap.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-            callbackWithoutCap.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-            verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId));
+            mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+            mCellAgent.addCapability(testCap);
+            mCellAgent.connect(true);
+            callbackWithCap.expectAvailableThenValidatedCallbacks(mCellAgent);
+            callbackWithoutCap.expectAvailableThenValidatedCallbacks(mCellAgent);
+            verify(mMockNetd).networkSetDefault(eq(mCellAgent.getNetwork().netId));
             reset(mMockNetd);
 
-            mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-            mWiFiNetworkAgent.addCapability(testCap);
-            mWiFiNetworkAgent.connect(true);
-            callbackWithCap.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
-            callbackWithoutCap.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
-            verify(mMockNetd).networkSetDefault(eq(mWiFiNetworkAgent.getNetwork().netId));
+            mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+            mWiFiAgent.addCapability(testCap);
+            mWiFiAgent.connect(true);
+            callbackWithCap.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
+            callbackWithoutCap.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
+            verify(mMockNetd).networkSetDefault(eq(mWiFiAgent.getNetwork().netId));
             reset(mMockNetd);
 
             // Remove the testing capability on wifi, verify the callback and default network
             // changes back to cellular.
-            mWiFiNetworkAgent.removeCapability(testCap);
-            callbackWithCap.expectAvailableCallbacksValidated(mCellNetworkAgent);
-            callbackWithoutCap.expectCapabilitiesWithout(testCap, mWiFiNetworkAgent);
-            verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId));
+            mWiFiAgent.removeCapability(testCap);
+            callbackWithCap.expectAvailableCallbacksValidated(mCellAgent);
+            callbackWithoutCap.expectCaps(mWiFiAgent, c -> !c.hasCapability(testCap));
+            verify(mMockNetd).networkSetDefault(eq(mCellAgent.getNetwork().netId));
             reset(mMockNetd);
 
-            mCellNetworkAgent.removeCapability(testCap);
-            callbackWithCap.expect(CallbackEntry.LOST, mCellNetworkAgent);
+            mCellAgent.removeCapability(testCap);
+            callbackWithCap.expect(LOST, mCellAgent);
             callbackWithoutCap.assertNoCallback();
             verify(mMockNetd).networkClearDefault();
 
@@ -10054,8 +10514,8 @@
     public final void testBatteryStatsNetworkType() throws Exception {
         final LinkProperties cellLp = new LinkProperties();
         cellLp.setInterfaceName("cell0");
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mCellAgent.connect(true);
         waitForIdle();
         final ArrayTrackRecord<ReportedInterfaces>.ReadHead readHead =
                 mDeps.mReportedInterfaceHistory.newReadHead();
@@ -10065,24 +10525,24 @@
 
         final LinkProperties wifiLp = new LinkProperties();
         wifiLp.setInterfaceName("wifi0");
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiAgent.connect(true);
         waitForIdle();
         assertNotNull(readHead.poll(TIMEOUT_MS, ri -> ri.contentEquals(mServiceContext,
                 wifiLp.getInterfaceName(),
                 new int[] { TRANSPORT_WIFI })));
 
-        mCellNetworkAgent.disconnect();
-        mWiFiNetworkAgent.disconnect();
+        mCellAgent.disconnect();
+        mWiFiAgent.disconnect();
 
         cellLp.setInterfaceName("wifi0");
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mCellAgent.connect(true);
         waitForIdle();
         assertNotNull(readHead.poll(TIMEOUT_MS, ri -> ri.contentEquals(mServiceContext,
                 cellLp.getInterfaceName(),
                 new int[] { TRANSPORT_CELLULAR })));
-        mCellNetworkAgent.disconnect();
+        mCellAgent.disconnect();
     }
 
     /**
@@ -10121,11 +10581,22 @@
         return event;
     }
 
+    private void verifyWakeupModifyInterface(String iface, boolean add) throws RemoteException {
+        if (add) {
+            verify(mMockNetd).wakeupAddInterface(eq(iface), anyString(), anyInt(),
+                    anyInt());
+        } else {
+            verify(mMockNetd).wakeupDelInterface(eq(iface), anyString(), anyInt(),
+                    anyInt());
+        }
+    }
+
     private <T> T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t) {
         if (inOrder != null) {
             return inOrder.verify(t);
         } else {
-            return verify(t);
+            // times(1) for consistency with the above. InOrder#verify always implies times(1).
+            return verify(t, times(1));
         }
     }
 
@@ -10139,7 +10610,7 @@
 
     private void verifyClatdStart(@Nullable InOrder inOrder, @NonNull String iface, int netId,
             @NonNull String nat64Prefix) throws Exception {
-        if (SdkLevel.isAtLeastT()) {
+        if (mDeps.isAtLeastT()) {
             verifyWithOrder(inOrder, mClatCoordinator)
                 .clatStart(eq(iface), eq(netId), eq(new IpPrefix(nat64Prefix)));
         } else {
@@ -10149,7 +10620,7 @@
 
     private void verifyNeverClatdStart(@Nullable InOrder inOrder, @NonNull String iface)
             throws Exception {
-        if (SdkLevel.isAtLeastT()) {
+        if (mDeps.isAtLeastT()) {
             verifyNeverWithOrder(inOrder, mClatCoordinator).clatStart(eq(iface), anyInt(), any());
         } else {
             verifyNeverWithOrder(inOrder, mMockNetd).clatdStart(eq(iface), anyString());
@@ -10158,7 +10629,7 @@
 
     private void verifyClatdStop(@Nullable InOrder inOrder, @NonNull String iface)
             throws Exception {
-        if (SdkLevel.isAtLeastT()) {
+        if (mDeps.isAtLeastT()) {
             verifyWithOrder(inOrder, mClatCoordinator).clatStop();
         } else {
             verifyWithOrder(inOrder, mMockNetd).clatdStop(eq(iface));
@@ -10167,13 +10638,28 @@
 
     private void verifyNeverClatdStop(@Nullable InOrder inOrder, @NonNull String iface)
             throws Exception {
-        if (SdkLevel.isAtLeastT()) {
+        if (mDeps.isAtLeastT()) {
             verifyNeverWithOrder(inOrder, mClatCoordinator).clatStop();
         } else {
             verifyNeverWithOrder(inOrder, mMockNetd).clatdStop(eq(iface));
         }
     }
 
+    private void expectNativeNetworkCreated(int netId, int permission, String iface,
+            InOrder inOrder) throws Exception {
+        verifyWithOrder(inOrder, mMockNetd).networkCreate(nativeNetworkConfigPhysical(netId,
+                permission));
+        verifyWithOrder(inOrder, mMockDnsResolver).createNetworkCache(eq(netId));
+        if (iface != null) {
+            verifyWithOrder(inOrder, mMockNetd).networkAddInterface(netId, iface);
+        }
+    }
+
+    private void expectNativeNetworkCreated(int netId, int permission, String iface)
+            throws Exception {
+        expectNativeNetworkCreated(netId, permission, iface, null /* inOrder */);
+    }
+
     @Test
     public void testStackedLinkProperties() throws Exception {
         final LinkAddress myIpv4 = new LinkAddress("1.2.3.4/24");
@@ -10203,26 +10689,23 @@
         cellLp.addLinkAddress(myIpv6);
         cellLp.addRoute(ipv6Default);
         cellLp.addRoute(ipv6Subnet);
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
         reset(mClatCoordinator);
 
         // Connect with ipv6 link properties. Expect prefix discovery to be started.
-        mCellNetworkAgent.connect(true);
-        int cellNetId = mCellNetworkAgent.getNetwork().netId;
+        mCellAgent.connect(true);
+        int cellNetId = mCellAgent.getNetwork().netId;
         waitForIdle();
 
-        verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(cellNetId,
-                INetd.PERMISSION_NONE));
+        expectNativeNetworkCreated(cellNetId, INetd.PERMISSION_NONE, MOBILE_IFNAME);
         assertRoutesAdded(cellNetId, ipv6Subnet, ipv6Default);
-        verify(mMockDnsResolver, times(1)).createNetworkCache(eq(cellNetId));
-        verify(mMockNetd, times(1)).networkAddInterface(cellNetId, MOBILE_IFNAME);
         final ArrayTrackRecord<ReportedInterfaces>.ReadHead readHead =
                 mDeps.mReportedInterfaceHistory.newReadHead();
         assertNotNull(readHead.poll(TIMEOUT_MS, ri -> ri.contentEquals(mServiceContext,
                 cellLp.getInterfaceName(),
                 new int[] { TRANSPORT_CELLULAR })));
 
-        networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        networkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
         verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId);
 
         // Switching default network updates TCP buffer sizes.
@@ -10230,8 +10713,8 @@
         // Add an IPv4 address. Expect prefix discovery to be stopped. Netd doesn't tell us that
         // the NAT64 prefix was removed because one was never discovered.
         cellLp.addLinkAddress(myIpv4);
-        mCellNetworkAgent.sendLinkProperties(cellLp);
-        networkCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+        mCellAgent.sendLinkProperties(cellLp);
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
         assertRoutesAdded(cellNetId, ipv4Subnet);
         verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId);
         verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(any());
@@ -10253,37 +10736,37 @@
 
         // Remove IPv4 address. Expect prefix discovery to be started again.
         cellLp.removeLinkAddress(myIpv4);
-        mCellNetworkAgent.sendLinkProperties(cellLp);
-        networkCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+        mCellAgent.sendLinkProperties(cellLp);
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
         verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId);
         assertRoutesRemoved(cellNetId, ipv4Subnet);
 
         // When NAT64 prefix discovery succeeds, LinkProperties are updated and clatd is started.
-        Nat464Xlat clat = getNat464Xlat(mCellNetworkAgent);
-        assertNull(mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getNat64Prefix());
+        Nat464Xlat clat = getNat464Xlat(mCellAgent);
+        assertNull(mCm.getLinkProperties(mCellAgent.getNetwork()).getNat64Prefix());
         mService.mResolverUnsolEventCallback.onNat64PrefixEvent(
                 makeNat64PrefixEvent(cellNetId, PREFIX_OPERATION_ADDED, kNat64PrefixString, 96));
         LinkProperties lpBeforeClat = networkCallback.expect(
-                CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent).getLp();
+                LINK_PROPERTIES_CHANGED, mCellAgent).getLp();
         assertEquals(0, lpBeforeClat.getStackedLinks().size());
         assertEquals(kNat64Prefix, lpBeforeClat.getNat64Prefix());
         verifyClatdStart(null /* inOrder */, MOBILE_IFNAME, cellNetId, kNat64Prefix.toString());
 
         // Clat iface comes up. Expect stacked link to be added.
         clat.interfaceLinkStateChanged(CLAT_MOBILE_IFNAME, true);
-        networkCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
-        List<LinkProperties> stackedLps = mCm.getLinkProperties(mCellNetworkAgent.getNetwork())
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
+        List<LinkProperties> stackedLps = mCm.getLinkProperties(mCellAgent.getNetwork())
                 .getStackedLinks();
         assertEquals(makeClatLinkProperties(myIpv4), stackedLps.get(0));
         assertRoutesAdded(cellNetId, stackedDefault);
         verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_MOBILE_IFNAME);
         // Change trivial linkproperties and see if stacked link is preserved.
         cellLp.addDnsServer(InetAddress.getByName("8.8.8.8"));
-        mCellNetworkAgent.sendLinkProperties(cellLp);
-        networkCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+        mCellAgent.sendLinkProperties(cellLp);
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
 
         List<LinkProperties> stackedLpsAfterChange =
-                mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getStackedLinks();
+                mCm.getLinkProperties(mCellAgent.getNetwork()).getStackedLinks();
         assertNotEquals(stackedLpsAfterChange, Collections.EMPTY_LIST);
         assertEquals(makeClatLinkProperties(myIpv4), stackedLpsAfterChange.get(0));
 
@@ -10306,19 +10789,19 @@
         // Expect clatd to be stopped and started with the new prefix.
         mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent(
                 cellNetId, PREFIX_OPERATION_ADDED, kOtherNat64PrefixString, 96));
-        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
-                (lp) -> lp.getStackedLinks().size() == 0);
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent,
+                cb -> cb.getLp().getStackedLinks().size() == 0);
         verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
         assertRoutesRemoved(cellNetId, stackedDefault);
         verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_MOBILE_IFNAME);
 
         verifyClatdStart(null /* inOrder */, MOBILE_IFNAME, cellNetId,
                 kOtherNat64Prefix.toString());
-        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
-                (lp) -> lp.getNat64Prefix().equals(kOtherNat64Prefix));
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent,
+                cb -> cb.getLp().getNat64Prefix().equals(kOtherNat64Prefix));
         clat.interfaceLinkStateChanged(CLAT_MOBILE_IFNAME, true);
-        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
-                (lp) -> lp.getStackedLinks().size() == 1);
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent,
+                cb -> cb.getLp().getStackedLinks().size() == 1);
         assertRoutesAdded(cellNetId, stackedDefault);
         verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_MOBILE_IFNAME);
         reset(mMockNetd);
@@ -10328,15 +10811,15 @@
         // linkproperties are cleaned up.
         cellLp.addLinkAddress(myIpv4);
         cellLp.addRoute(ipv4Subnet);
-        mCellNetworkAgent.sendLinkProperties(cellLp);
-        networkCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+        mCellAgent.sendLinkProperties(cellLp);
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
         assertRoutesAdded(cellNetId, ipv4Subnet);
         verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
         verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId);
 
         // As soon as stop is called, the linkproperties lose the stacked interface.
-        networkCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
-        LinkProperties actualLpAfterIpv4 = mCm.getLinkProperties(mCellNetworkAgent.getNetwork());
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
+        LinkProperties actualLpAfterIpv4 = mCm.getLinkProperties(mCellAgent.getNetwork());
         LinkProperties expected = new LinkProperties(cellLp);
         expected.setNat64Prefix(kOtherNat64Prefix);
         assertEquals(expected, actualLpAfterIpv4);
@@ -10347,6 +10830,11 @@
         clat.interfaceRemoved(CLAT_MOBILE_IFNAME);
         networkCallback.assertNoCallback();
         verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_MOBILE_IFNAME);
+
+        if (mDeps.isAtLeastU()) {
+            verifyWakeupModifyInterface(CLAT_MOBILE_IFNAME, false);
+        }
+
         verifyNoMoreInteractions(mMockNetd);
         verifyNoMoreInteractions(mClatCoordinator);
         verifyNoMoreInteractions(mMockDnsResolver);
@@ -10359,49 +10847,70 @@
         // Stopping prefix discovery causes netd to tell us that the NAT64 prefix is gone.
         mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent(
                 cellNetId, PREFIX_OPERATION_REMOVED, kOtherNat64PrefixString, 96));
-        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
-                (lp) -> lp.getNat64Prefix() == null);
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent,
+                cb -> cb.getLp().getNat64Prefix() == null);
 
         // Remove IPv4 address and expect prefix discovery and clatd to be started again.
         cellLp.removeLinkAddress(myIpv4);
         cellLp.removeRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME));
         cellLp.removeDnsServer(InetAddress.getByName("8.8.8.8"));
-        mCellNetworkAgent.sendLinkProperties(cellLp);
-        networkCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+        mCellAgent.sendLinkProperties(cellLp);
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
         assertRoutesRemoved(cellNetId, ipv4Subnet);  // Directly-connected routes auto-added.
         verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId);
         mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent(
                 cellNetId, PREFIX_OPERATION_ADDED, kNat64PrefixString, 96));
-        networkCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
         verifyClatdStart(null /* inOrder */, MOBILE_IFNAME, cellNetId, kNat64Prefix.toString());
 
         // Clat iface comes up. Expect stacked link to be added.
         clat.interfaceLinkStateChanged(CLAT_MOBILE_IFNAME, true);
-        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
-                (lp) -> lp.getStackedLinks().size() == 1 && lp.getNat64Prefix() != null);
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent,
+                cb -> cb.getLp().getStackedLinks().size() == 1
+                        && cb.getLp().getNat64Prefix() != null);
         assertRoutesAdded(cellNetId, stackedDefault);
         verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_MOBILE_IFNAME);
 
+        if (mDeps.isAtLeastU()) {
+            verifyWakeupModifyInterface(CLAT_MOBILE_IFNAME, true);
+        }
+
         // NAT64 prefix is removed. Expect that clat is stopped.
         mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent(
                 cellNetId, PREFIX_OPERATION_REMOVED, kNat64PrefixString, 96));
-        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
-                (lp) -> lp.getStackedLinks().size() == 0 && lp.getNat64Prefix() == null);
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent,
+                cb -> cb.getLp().getStackedLinks().size() == 0
+                        && cb.getLp().getNat64Prefix() == null);
         assertRoutesRemoved(cellNetId, ipv4Subnet, stackedDefault);
 
         // Stop has no effect because clat is already stopped.
         verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
-        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
-                (lp) -> lp.getStackedLinks().size() == 0);
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent,
+                cb -> cb.getLp().getStackedLinks().size() == 0);
         verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_MOBILE_IFNAME);
         verify(mMockNetd, times(1)).interfaceGetCfg(CLAT_MOBILE_IFNAME);
+
+        if (mDeps.isAtLeastU()) {
+            verifyWakeupModifyInterface(CLAT_MOBILE_IFNAME, false);
+        }
+
         // Clean up.
-        mCellNetworkAgent.disconnect();
-        networkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        mCellAgent.disconnect();
+        networkCallback.expect(LOST, mCellAgent);
         networkCallback.assertNoCallback();
         verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
                 eq(Integer.toString(TRANSPORT_CELLULAR)));
         verify(mMockNetd).networkDestroy(cellNetId);
+        if (mDeps.isAtLeastU()) {
+            verify(mMockNetd).setNetworkAllowlist(any());
+        } else {
+            verify(mMockNetd, never()).setNetworkAllowlist(any());
+        }
+
+        if (mDeps.isAtLeastU()) {
+            verifyWakeupModifyInterface(MOBILE_IFNAME, false);
+        }
+
         verifyNoMoreInteractions(mMockNetd);
         verifyNoMoreInteractions(mClatCoordinator);
         reset(mMockNetd);
@@ -10413,44 +10922,65 @@
         doReturn(getClatInterfaceConfigParcel(myIpv4)).when(mMockNetd)
                 .interfaceGetCfg(CLAT_MOBILE_IFNAME);
         cellLp.setNat64Prefix(kNat64Prefix);
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
-        mCellNetworkAgent.connect(false /* validated */);
-        networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
-        cellNetId = mCellNetworkAgent.getNetwork().netId;
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mCellAgent.connect(false /* validated */);
+        networkCallback.expectAvailableCallbacksUnvalidated(mCellAgent);
+        cellNetId = mCellAgent.getNetwork().netId;
         verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(cellNetId,
                 INetd.PERMISSION_NONE));
         assertRoutesAdded(cellNetId, ipv6Subnet, ipv6Default);
 
         // Clatd is started and clat iface comes up. Expect stacked link to be added.
         verifyClatdStart(null /* inOrder */, MOBILE_IFNAME, cellNetId, kNat64Prefix.toString());
-        clat = getNat464Xlat(mCellNetworkAgent);
+        clat = getNat464Xlat(mCellAgent);
         clat.interfaceLinkStateChanged(CLAT_MOBILE_IFNAME, true /* up */);
-        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
-                (lp) -> lp.getStackedLinks().size() == 1
-                        && lp.getNat64Prefix().equals(kNat64Prefix));
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent,
+                cb -> cb.getLp().getStackedLinks().size() == 1
+                        && cb.getLp().getNat64Prefix().equals(kNat64Prefix));
         verify(mMockNetd).networkAddInterface(cellNetId, CLAT_MOBILE_IFNAME);
         // assertRoutesAdded sees all calls since last mMockNetd reset, so expect IPv6 routes again.
         assertRoutesAdded(cellNetId, ipv6Subnet, ipv6Default, stackedDefault);
+
+        if (mDeps.isAtLeastU()) {
+            verifyWakeupModifyInterface(MOBILE_IFNAME, true);
+        }
+
         reset(mMockNetd);
         reset(mClatCoordinator);
 
         // Disconnect the network. clat is stopped and the network is destroyed.
-        mCellNetworkAgent.disconnect();
-        networkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        mCellAgent.disconnect();
+        networkCallback.expect(LOST, mCellAgent);
         networkCallback.assertNoCallback();
         verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
+
+        if (mDeps.isAtLeastU()) {
+            verifyWakeupModifyInterface(CLAT_MOBILE_IFNAME, false);
+        }
+
         verify(mMockNetd).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
                 eq(Integer.toString(TRANSPORT_CELLULAR)));
         verify(mMockNetd).networkDestroy(cellNetId);
+        if (mDeps.isAtLeastU()) {
+            verify(mMockNetd).setNetworkAllowlist(any());
+        } else {
+            verify(mMockNetd, never()).setNetworkAllowlist(any());
+        }
+
+        if (mDeps.isAtLeastU()) {
+            verifyWakeupModifyInterface(MOBILE_IFNAME, false);
+        }
+
         verifyNoMoreInteractions(mMockNetd);
         verifyNoMoreInteractions(mClatCoordinator);
 
         mCm.unregisterNetworkCallback(networkCallback);
     }
 
-    private void expectNat64PrefixChange(TestableNetworkCallback callback,
+    private void expectNat64PrefixChange(TestNetworkCallback callback,
             TestNetworkAgentWrapper agent, IpPrefix prefix) {
-        callback.expectLinkPropertiesThat(agent, x -> Objects.equals(x.getNat64Prefix(), prefix));
+        callback.expect(LINK_PROPERTIES_CHANGED, agent,
+                x -> Objects.equals(x.getLp().getNat64Prefix(), prefix));
     }
 
     @Test
@@ -10480,11 +11010,11 @@
         // prefix discovery is never started.
         LinkProperties lp = new LinkProperties(baseLp);
         lp.setNat64Prefix(pref64FromRa);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
-        mWiFiNetworkAgent.connect(false);
-        final Network network = mWiFiNetworkAgent.getNetwork();
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
+        mWiFiAgent.connect(false);
+        final Network network = mWiFiAgent.getNetwork();
         int netId = network.getNetId();
-        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
         verifyClatdStart(inOrder, iface, netId, pref64FromRa.toString());
         inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString());
         inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId);
@@ -10493,8 +11023,8 @@
 
         // If the RA prefix is withdrawn, clatd is stopped and prefix discovery is started.
         lp.setNat64Prefix(null);
-        mWiFiNetworkAgent.sendLinkProperties(lp);
-        expectNat64PrefixChange(callback, mWiFiNetworkAgent, null);
+        mWiFiAgent.sendLinkProperties(lp);
+        expectNat64PrefixChange(callback, mWiFiAgent, null);
         verifyClatdStop(inOrder, iface);
         inOrder.verify(mMockDnsResolver).setPrefix64(netId, "");
         inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId);
@@ -10502,8 +11032,8 @@
         // If the RA prefix appears while DNS discovery is in progress, discovery is stopped and
         // clatd is started with the prefix from the RA.
         lp.setNat64Prefix(pref64FromRa);
-        mWiFiNetworkAgent.sendLinkProperties(lp);
-        expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromRa);
+        mWiFiAgent.sendLinkProperties(lp);
+        expectNat64PrefixChange(callback, mWiFiAgent, pref64FromRa);
         verifyClatdStart(inOrder, iface, netId, pref64FromRa.toString());
         inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId);
         inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString());
@@ -10511,21 +11041,21 @@
         // Withdraw the RA prefix so we can test the case where an RA prefix appears after DNS
         // discovery has succeeded.
         lp.setNat64Prefix(null);
-        mWiFiNetworkAgent.sendLinkProperties(lp);
-        expectNat64PrefixChange(callback, mWiFiNetworkAgent, null);
+        mWiFiAgent.sendLinkProperties(lp);
+        expectNat64PrefixChange(callback, mWiFiAgent, null);
         verifyClatdStop(inOrder, iface);
         inOrder.verify(mMockDnsResolver).setPrefix64(netId, "");
         inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId);
 
         mService.mResolverUnsolEventCallback.onNat64PrefixEvent(
                 makeNat64PrefixEvent(netId, PREFIX_OPERATION_ADDED, pref64FromDnsStr, 96));
-        expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromDns);
+        expectNat64PrefixChange(callback, mWiFiAgent, pref64FromDns);
         verifyClatdStart(inOrder, iface, netId, pref64FromDns.toString());
 
         // If an RA advertises the same prefix that was discovered by DNS, nothing happens: prefix
         // discovery is not stopped, and there are no callbacks.
         lp.setNat64Prefix(pref64FromDns);
-        mWiFiNetworkAgent.sendLinkProperties(lp);
+        mWiFiAgent.sendLinkProperties(lp);
         callback.assertNoCallback();
         verifyNeverClatdStop(inOrder, iface);
         verifyNeverClatdStart(inOrder, iface);
@@ -10535,7 +11065,7 @@
 
         // If the RA is later withdrawn, nothing happens again.
         lp.setNat64Prefix(null);
-        mWiFiNetworkAgent.sendLinkProperties(lp);
+        mWiFiAgent.sendLinkProperties(lp);
         callback.assertNoCallback();
         verifyNeverClatdStop(inOrder, iface);
         verifyNeverClatdStart(inOrder, iface);
@@ -10545,8 +11075,8 @@
 
         // If the RA prefix changes, clatd is restarted and prefix discovery is stopped.
         lp.setNat64Prefix(pref64FromRa);
-        mWiFiNetworkAgent.sendLinkProperties(lp);
-        expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromRa);
+        mWiFiAgent.sendLinkProperties(lp);
+        expectNat64PrefixChange(callback, mWiFiAgent, pref64FromRa);
         verifyClatdStop(inOrder, iface);
         inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId);
 
@@ -10560,8 +11090,8 @@
 
         // If the RA prefix changes, clatd is restarted and prefix discovery is not started.
         lp.setNat64Prefix(newPref64FromRa);
-        mWiFiNetworkAgent.sendLinkProperties(lp);
-        expectNat64PrefixChange(callback, mWiFiNetworkAgent, newPref64FromRa);
+        mWiFiAgent.sendLinkProperties(lp);
+        expectNat64PrefixChange(callback, mWiFiAgent, newPref64FromRa);
         verifyClatdStop(inOrder, iface);
         inOrder.verify(mMockDnsResolver).setPrefix64(netId, "");
         verifyClatdStart(inOrder, iface, netId, newPref64FromRa.toString());
@@ -10571,7 +11101,7 @@
 
         // If the RA prefix changes to the same value, nothing happens.
         lp.setNat64Prefix(newPref64FromRa);
-        mWiFiNetworkAgent.sendLinkProperties(lp);
+        mWiFiAgent.sendLinkProperties(lp);
         callback.assertNoCallback();
         assertEquals(newPref64FromRa, mCm.getLinkProperties(network).getNat64Prefix());
         verifyNeverClatdStop(inOrder, iface);
@@ -10585,19 +11115,19 @@
         // If the same prefix is learned first by DNS and then by RA, and clat is later stopped,
         // (e.g., because the network disconnects) setPrefix64(netid, "") is never called.
         lp.setNat64Prefix(null);
-        mWiFiNetworkAgent.sendLinkProperties(lp);
-        expectNat64PrefixChange(callback, mWiFiNetworkAgent, null);
+        mWiFiAgent.sendLinkProperties(lp);
+        expectNat64PrefixChange(callback, mWiFiAgent, null);
         verifyClatdStop(inOrder, iface);
         inOrder.verify(mMockDnsResolver).setPrefix64(netId, "");
         inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId);
         mService.mResolverUnsolEventCallback.onNat64PrefixEvent(
                 makeNat64PrefixEvent(netId, PREFIX_OPERATION_ADDED, pref64FromDnsStr, 96));
-        expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromDns);
+        expectNat64PrefixChange(callback, mWiFiAgent, pref64FromDns);
         verifyClatdStart(inOrder, iface, netId, pref64FromDns.toString());
         inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), any());
 
         lp.setNat64Prefix(pref64FromDns);
-        mWiFiNetworkAgent.sendLinkProperties(lp);
+        mWiFiAgent.sendLinkProperties(lp);
         callback.assertNoCallback();
         verifyNeverClatdStop(inOrder, iface);
         verifyNeverClatdStart(inOrder, iface);
@@ -10609,8 +11139,8 @@
         // before CONNECTIVITY_ACTION is sent. Wait for CONNECTIVITY_ACTION before verifying that
         // clat has been stopped, or the test will be flaky.
         ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED);
-        mWiFiNetworkAgent.disconnect();
-        callback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        mWiFiAgent.disconnect();
+        callback.expect(LOST, mWiFiAgent);
         b.expectBroadcast();
 
         verifyClatdStop(inOrder, iface);
@@ -10637,17 +11167,17 @@
         cellLp.setInterfaceName(MOBILE_IFNAME);
         cellLp.addLinkAddress(new LinkAddress("2001:db8:1::1/64"));
         cellLp.addRoute(new RouteInfo(new IpPrefix("::/0"), null, MOBILE_IFNAME));
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
 
-        mCellNetworkAgent.sendLinkProperties(cellLp);
-        mCellNetworkAgent.connect(true);
-        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        final int cellNetId = mCellNetworkAgent.getNetwork().netId;
+        mCellAgent.sendLinkProperties(cellLp);
+        mCellAgent.connect(true);
+        callback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        defaultCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        final int cellNetId = mCellAgent.getNetwork().netId;
         waitForIdle();
 
         verify(mMockDnsResolver, never()).startPrefix64Discovery(cellNetId);
-        Nat464Xlat clat = getNat464Xlat(mCellNetworkAgent);
+        Nat464Xlat clat = getNat464Xlat(mCellAgent);
         assertTrue("Nat464Xlat was not IDLE", !clat.isStarted());
 
         // This cannot happen because prefix discovery cannot succeed if it is never started.
@@ -10667,25 +11197,25 @@
                 .build();
         mCm.registerNetworkCallback(networkRequest, networkCallback);
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         final LinkProperties cellLp = new LinkProperties();
         cellLp.setInterfaceName(MOBILE_IFNAME);
-        mCellNetworkAgent.sendLinkProperties(cellLp);
-        mCellNetworkAgent.connect(true);
-        networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent.sendLinkProperties(cellLp);
+        mCellAgent.connect(true);
+        networkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
         verify(mMockNetd, times(1)).idletimerAddInterface(eq(MOBILE_IFNAME), anyInt(),
                 eq(Integer.toString(TRANSPORT_CELLULAR)));
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         final LinkProperties wifiLp = new LinkProperties();
         wifiLp.setInterfaceName(WIFI_IFNAME);
-        mWiFiNetworkAgent.sendLinkProperties(wifiLp);
+        mWiFiAgent.sendLinkProperties(wifiLp);
 
         // Network switch
-        mWiFiNetworkAgent.connect(true);
-        networkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        networkCallback.expectLosing(mCellNetworkAgent);
-        networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        mWiFiAgent.connect(true);
+        networkCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        networkCallback.expectLosing(mCellAgent);
+        networkCallback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
         verify(mMockNetd, times(1)).idletimerAddInterface(eq(WIFI_IFNAME), anyInt(),
                 eq(Integer.toString(TRANSPORT_WIFI)));
         verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
@@ -10693,8 +11223,8 @@
 
         // Disconnect wifi and switch back to cell
         reset(mMockNetd);
-        mWiFiNetworkAgent.disconnect();
-        networkCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+        mWiFiAgent.disconnect();
+        networkCallback.expect(LOST, mWiFiAgent);
         assertNoCallbacks(networkCallback);
         verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(WIFI_IFNAME), anyInt(),
                 eq(Integer.toString(TRANSPORT_WIFI)));
@@ -10703,13 +11233,13 @@
 
         // reconnect wifi
         reset(mMockNetd);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         wifiLp.setInterfaceName(WIFI_IFNAME);
-        mWiFiNetworkAgent.sendLinkProperties(wifiLp);
-        mWiFiNetworkAgent.connect(true);
-        networkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        networkCallback.expectLosing(mCellNetworkAgent);
-        networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        mWiFiAgent.sendLinkProperties(wifiLp);
+        mWiFiAgent.connect(true);
+        networkCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        networkCallback.expectLosing(mCellAgent);
+        networkCallback.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
         verify(mMockNetd, times(1)).idletimerAddInterface(eq(WIFI_IFNAME), anyInt(),
                 eq(Integer.toString(TRANSPORT_WIFI)));
         verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
@@ -10717,21 +11247,20 @@
 
         // Disconnect cell
         reset(mMockNetd);
-        mCellNetworkAgent.disconnect();
-        networkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        mCellAgent.disconnect();
+        networkCallback.expect(LOST, mCellAgent);
         // LOST callback is triggered earlier than removing idle timer. Broadcast should also be
         // sent as network being switched. Ensure rule removal for cell will not be triggered
         // unexpectedly before network being removed.
         waitForIdle();
         verify(mMockNetd, times(0)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
                 eq(Integer.toString(TRANSPORT_CELLULAR)));
-        verify(mMockNetd, times(1)).networkDestroy(eq(mCellNetworkAgent.getNetwork().netId));
-        verify(mMockDnsResolver, times(1))
-                .destroyNetworkCache(eq(mCellNetworkAgent.getNetwork().netId));
+        verify(mMockNetd, times(1)).networkDestroy(eq(mCellAgent.getNetwork().netId));
+        verify(mMockDnsResolver, times(1)).destroyNetworkCache(eq(mCellAgent.getNetwork().netId));
 
         // Disconnect wifi
         ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED);
-        mWiFiNetworkAgent.disconnect();
+        mWiFiAgent.disconnect();
         b.expectBroadcast();
         verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(WIFI_IFNAME), anyInt(),
                 eq(Integer.toString(TRANSPORT_WIFI)));
@@ -10758,21 +11287,21 @@
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
         mCm.registerNetworkCallback(networkRequest, networkCallback);
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         reset(mMockNetd);
         // Switching default network updates TCP buffer sizes.
-        mCellNetworkAgent.connect(false);
-        networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        mCellAgent.connect(false);
+        networkCallback.expectAvailableCallbacksUnvalidated(mCellAgent);
         verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES);
         // Change link Properties should have updated tcp buffer size.
         LinkProperties lp = new LinkProperties();
         lp.setTcpBufferSizes(testTcpBufferSizes);
-        mCellNetworkAgent.sendLinkProperties(lp);
-        networkCallback.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
+        mCellAgent.sendLinkProperties(lp);
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent);
         verifyTcpBufferSizeChange(testTcpBufferSizes);
         // Clean up.
-        mCellNetworkAgent.disconnect();
-        networkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        mCellAgent.disconnect();
+        networkCallback.expect(LOST, mCellAgent);
         networkCallback.assertNoCallback();
         mCm.unregisterNetworkCallback(networkCallback);
     }
@@ -10780,8 +11309,8 @@
     @Test
     public void testGetGlobalProxyForNetwork() throws Exception {
         final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        final Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        final Network wifiNetwork = mWiFiAgent.getNetwork();
         mProxyTracker.setGlobalProxy(testProxyInfo);
         assertEquals(testProxyInfo, mService.getProxyForNetwork(wifiNetwork));
     }
@@ -10789,15 +11318,15 @@
     @Test
     public void testGetProxyForActiveNetwork() throws Exception {
         final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
         waitForIdle();
         assertNull(mService.getProxyForNetwork(null));
 
         final LinkProperties testLinkProperties = new LinkProperties();
         testLinkProperties.setHttpProxy(testProxyInfo);
 
-        mWiFiNetworkAgent.sendLinkProperties(testLinkProperties);
+        mWiFiAgent.sendLinkProperties(testLinkProperties);
         waitForIdle();
 
         assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
@@ -10808,8 +11337,8 @@
         final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
 
         // Set up a WiFi network with no proxy
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
         waitForIdle();
         assertNull(mService.getProxyForNetwork(null));
 
@@ -10822,7 +11351,7 @@
         // Test that the VPN network returns a proxy, and the WiFi does not.
         assertEquals(testProxyInfo, mService.getProxyForNetwork(mMockVpn.getNetwork()));
         assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
-        assertNull(mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
+        assertNull(mService.getProxyForNetwork(mWiFiAgent.getNetwork()));
 
         // Test that the VPN network returns no proxy when it is set to null.
         testLinkProperties.setHttpProxy(null);
@@ -10833,7 +11362,7 @@
 
         // Set WiFi proxy and check that the vpn proxy is still null.
         testLinkProperties.setHttpProxy(testProxyInfo);
-        mWiFiNetworkAgent.sendLinkProperties(testLinkProperties);
+        mWiFiAgent.sendLinkProperties(testLinkProperties);
         waitForIdle();
         assertNull(mService.getProxyForNetwork(null));
 
@@ -10841,8 +11370,8 @@
         // correct proxy setting.
         mMockVpn.disconnect();
         waitForIdle();
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
-        assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiAgent.getNetwork()));
         assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
     }
 
@@ -10863,7 +11392,6 @@
         verify(mBpfNetMaps, times(2)).addUidInterfaceRules(eq("tun0"), uidCaptor.capture());
         assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
         assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
-        assertTrue(mService.mPermissionMonitor.getVpnInterfaceUidRanges("tun0").equals(vpnRange));
 
         mMockVpn.disconnect();
         waitForIdle();
@@ -10871,7 +11399,6 @@
         // Disconnected VPN should have interface rules removed
         verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
         assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
-        assertNull(mService.mPermissionMonitor.getVpnInterfaceUidRanges("tun0"));
     }
 
     private void checkInterfaceFilteringRuleWithNullInterface(final LinkProperties lp,
@@ -10881,7 +11408,7 @@
         mMockVpn.establish(lp, uid, vpnRange);
         assertVpnUidRangesUpdated(true, vpnRange, uid);
 
-        if (SdkLevel.isAtLeastT()) {
+        if (mDeps.isAtLeastT()) {
             // On T and above, VPN should have rules for null interface. Null Interface is a
             // wildcard and this accepts traffic from all the interfaces.
             // There are two expected invocations, one during the VPN initial
@@ -10896,8 +11423,6 @@
                 assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID, VPN_UID);
                 assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID, VPN_UID);
             }
-            assertEquals(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */),
-                    vpnRange);
 
             mMockVpn.disconnect();
             waitForIdle();
@@ -10909,7 +11434,6 @@
             } else {
                 assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID, VPN_UID);
             }
-            assertNull(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */));
         } else {
             // Before T, rules are not configured for null interface.
             verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
@@ -11021,15 +11545,15 @@
 
     @Test
     public void testLinkPropertiesWithWakeOnLanForActiveNetwork() throws Exception {
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
 
         LinkProperties wifiLp = new LinkProperties();
         wifiLp.setInterfaceName(WIFI_WOL_IFNAME);
         wifiLp.setWakeOnLanSupported(false);
 
         // Default network switch should update ifaces.
-        mWiFiNetworkAgent.connect(false);
-        mWiFiNetworkAgent.sendLinkProperties(wifiLp);
+        mWiFiAgent.connect(false);
+        mWiFiAgent.sendLinkProperties(wifiLp);
         waitForIdle();
 
         // ConnectivityService should have changed the WakeOnLanSupported to true
@@ -11421,20 +11945,19 @@
                 .addTransportType(TRANSPORT_WIFI).build();
         mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, new LinkProperties(),
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, new LinkProperties(),
                 ncTemplate);
-        mWiFiNetworkAgent.connect(false);
+        mWiFiAgent.connect(false);
 
-        wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
 
         // Send network capabilities update with TransportInfo to trigger capabilities changed
         // callback.
-        mWiFiNetworkAgent.setNetworkCapabilities(
-                ncTemplate.setTransportInfo(actualTransportInfo), true);
+        mWiFiAgent.setNetworkCapabilities(ncTemplate.setTransportInfo(actualTransportInfo), true);
 
-        wifiNetworkCallback.expectCapabilitiesThat(mWiFiNetworkAgent,
-                nc -> Objects.equals(expectedOwnerUid, nc.getOwnerUid())
-                        && Objects.equals(expectedTransportInfo, nc.getTransportInfo()));
+        wifiNetworkCallback.expectCaps(mWiFiAgent,
+                c -> Objects.equals(expectedOwnerUid, c.getOwnerUid())
+                        && Objects.equals(expectedTransportInfo, c.getTransportInfo()));
     }
 
     @Test
@@ -11461,32 +11984,31 @@
                 .addTransportType(TRANSPORT_WIFI)
                 .setTransportInfo(new TestTransportInfo());
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, new LinkProperties(),
-                ncTemplate);
-        mWiFiNetworkAgent.connect(true /* validated; waits for callback */);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, new LinkProperties(), ncTemplate);
+        mWiFiAgent.connect(true /* validated; waits for callback */);
 
         // NETWORK_SETTINGS redaction is controlled by the NETWORK_SETTINGS permission
-        assertTrue(getTestTransportInfo(mWiFiNetworkAgent).settingsRedacted);
+        assertTrue(getTestTransportInfo(mWiFiAgent).settingsRedacted);
         withPermission(NETWORK_SETTINGS, () -> {
-            assertFalse(getTestTransportInfo(mWiFiNetworkAgent).settingsRedacted);
+            assertFalse(getTestTransportInfo(mWiFiAgent).settingsRedacted);
         });
-        assertTrue(getTestTransportInfo(mWiFiNetworkAgent).settingsRedacted);
+        assertTrue(getTestTransportInfo(mWiFiAgent).settingsRedacted);
 
         // LOCAL_MAC_ADDRESS redaction is controlled by the LOCAL_MAC_ADDRESS permission
-        assertTrue(getTestTransportInfo(mWiFiNetworkAgent).localMacAddressRedacted);
+        assertTrue(getTestTransportInfo(mWiFiAgent).localMacAddressRedacted);
         withPermission(LOCAL_MAC_ADDRESS, () -> {
-            assertFalse(getTestTransportInfo(mWiFiNetworkAgent).localMacAddressRedacted);
+            assertFalse(getTestTransportInfo(mWiFiAgent).localMacAddressRedacted);
         });
-        assertTrue(getTestTransportInfo(mWiFiNetworkAgent).localMacAddressRedacted);
+        assertTrue(getTestTransportInfo(mWiFiAgent).localMacAddressRedacted);
 
         // Synchronous getNetworkCapabilities calls never return unredacted location-sensitive
         // information.
-        assertTrue(getTestTransportInfo(mWiFiNetworkAgent).locationRedacted);
+        assertTrue(getTestTransportInfo(mWiFiAgent).locationRedacted);
         setupLocationPermissions(Build.VERSION_CODES.S, true, AppOpsManager.OPSTR_FINE_LOCATION,
                 Manifest.permission.ACCESS_FINE_LOCATION);
-        assertTrue(getTestTransportInfo(mWiFiNetworkAgent).locationRedacted);
+        assertTrue(getTestTransportInfo(mWiFiAgent).locationRedacted);
         denyAllLocationPrivilegedPermissions();
-        assertTrue(getTestTransportInfo(mWiFiNetworkAgent).locationRedacted);
+        assertTrue(getTestTransportInfo(mWiFiAgent).locationRedacted);
     }
 
     private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType)
@@ -11875,7 +12397,7 @@
 
     @Test
     public void testUnderlyingNetworksWillBeSetInNetworkAgentInfoConstructor() throws Exception {
-        assumeTrue(SdkLevel.isAtLeastT());
+        assumeTrue(mDeps.isAtLeastT());
         final Network network1 = new Network(100);
         final Network network2 = new Network(101);
         final List<Network> underlyingNetworks = new ArrayList<>();
@@ -11911,9 +12433,9 @@
         mCm.registerDefaultNetworkCallback(callback);
         final LinkProperties linkProperties = new LinkProperties();
         linkProperties.setInterfaceName(INTERFACE_NAME);
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, linkProperties);
-        mCellNetworkAgent.connect(true);
-        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, linkProperties);
+        mCellAgent.connect(true);
+        callback.expectAvailableThenValidatedCallbacks(mCellAgent);
         callback.assertNoCallback();
 
         final NetworkRequest request = new NetworkRequest.Builder().build();
@@ -11950,10 +12472,10 @@
         final NetworkCapabilities ncTemplate = new NetworkCapabilities()
                 .addTransportType(TRANSPORT_CELLULAR)
                 .setTransportInfo(new TestTransportInfo());
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(),
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(),
                 ncTemplate);
-        mCellNetworkAgent.connect(true);
-        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent.connect(true);
+        callback.expectAvailableThenValidatedCallbacks(mCellAgent);
         callback.assertNoCallback();
 
         // Make sure a report is sent and that the caps are suitably redacted.
@@ -11979,7 +12501,7 @@
 
         // Trigger notifyDataStallSuspected() on the INetworkMonitorCallbacks instance in the
         // cellular network agent
-        mCellNetworkAgent.notifyDataStallSuspected();
+        mCellAgent.notifyDataStallSuspected();
 
         // Verify onDataStallSuspected fired
         verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS)).onDataStallSuspected(
@@ -11990,7 +12512,7 @@
     public void testConnectivityDiagnosticsCallbackOnConnectivityReported() throws Exception {
         setUpConnectivityDiagnosticsCallback();
 
-        final Network n = mCellNetworkAgent.getNetwork();
+        final Network n = mCellAgent.getNetwork();
         final boolean hasConnectivity = true;
         mService.reportNetworkConnectivity(n, hasConnectivity);
 
@@ -12023,7 +12545,7 @@
 
         // report known Connectivity from a different uid. Verify that network is not re-validated
         // and this callback is not notified.
-        final Network n = mCellNetworkAgent.getNetwork();
+        final Network n = mCellAgent.getNetwork();
         final boolean hasConnectivity = true;
         doAsUid(Process.myUid() + 1, () -> mService.reportNetworkConnectivity(n, hasConnectivity));
 
@@ -12075,11 +12597,11 @@
         final NetworkRequest request = new NetworkRequest.Builder().build();
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
         mCm.registerNetworkCallback(request, networkCallback);
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         reset(mMockNetd);
-        mCellNetworkAgent.connect(false);
-        networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
-        final int netId = mCellNetworkAgent.getNetwork().netId;
+        mCellAgent.connect(false);
+        networkCallback.expectAvailableCallbacksUnvalidated(mCellAgent);
+        final int netId = mCellAgent.getNetwork().netId;
 
         final String iface = "rmnet_data0";
         final InetAddress gateway = InetAddress.getByName("fe80::5678");
@@ -12096,8 +12618,9 @@
         lp.addRoute(direct);
         lp.addRoute(rio1);
         lp.addRoute(defaultRoute);
-        mCellNetworkAgent.sendLinkProperties(lp);
-        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, x -> x.getRoutes().size() == 3);
+        mCellAgent.sendLinkProperties(lp);
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent,
+                x -> x.getLp().getRoutes().size() == 3);
 
         assertRoutesAdded(netId, direct, rio1, defaultRoute);
         reset(mMockNetd);
@@ -12111,9 +12634,9 @@
         assertFalse(lp.getRoutes().contains(defaultRoute));
         assertTrue(lp.getRoutes().contains(defaultWithMtu));
 
-        mCellNetworkAgent.sendLinkProperties(lp);
-        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
-                x -> x.getRoutes().contains(rio2));
+        mCellAgent.sendLinkProperties(lp);
+        networkCallback.expect(LINK_PROPERTIES_CHANGED, mCellAgent,
+                x -> x.getLp().getRoutes().contains(rio2));
 
         assertRoutesRemoved(netId, rio1);
         assertRoutesAdded(netId, rio2);
@@ -12200,12 +12723,11 @@
 
     private void assertVpnUidRangesUpdated(boolean add, Set<UidRange> vpnRanges, int exemptUid)
             throws Exception {
-        InOrder inOrder = inOrder(mMockNetd);
-        ArgumentCaptor<int[]> exemptUidCaptor = ArgumentCaptor.forClass(int[].class);
+        InOrder inOrder = inOrder(mMockNetd, mDestroySocketsWrapper);
+        final Set<Integer> exemptUidSet = new ArraySet<>(List.of(exemptUid, Process.VPN_UID));
 
-        inOrder.verify(mMockNetd, times(1)).socketDestroy(eq(toUidRangeStableParcels(vpnRanges)),
-                exemptUidCaptor.capture());
-        assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid);
+        inOrder.verify(mDestroySocketsWrapper).destroyLiveTcpSockets(
+                UidRange.toIntRanges(vpnRanges), exemptUidSet);
 
         if (add) {
             inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(
@@ -12217,20 +12739,19 @@
                             toUidRangeStableParcels(vpnRanges), PREFERENCE_ORDER_VPN));
         }
 
-        inOrder.verify(mMockNetd, times(1)).socketDestroy(eq(toUidRangeStableParcels(vpnRanges)),
-                exemptUidCaptor.capture());
-        assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid);
+        inOrder.verify(mDestroySocketsWrapper).destroyLiveTcpSockets(
+                UidRange.toIntRanges(vpnRanges), exemptUidSet);
     }
 
     @Test
     public void testVpnUidRangesUpdate() throws Exception {
         // Set up a WiFi network without proxy.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
         assertNull(mService.getProxyForNetwork(null));
         assertNull(mCm.getDefaultProxy());
 
-        final ExpectedBroadcast b1 = registerPacProxyBroadcast();
+        final ExpectedBroadcast b1 = expectProxyChangeAction();
         final LinkProperties lp = new LinkProperties();
         lp.setInterfaceName("tun0");
         lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
@@ -12243,7 +12764,7 @@
         b1.expectNoBroadcast(500);
 
         // Update to new range which is old range minus APP1, i.e. only APP2
-        final ExpectedBroadcast b2 = registerPacProxyBroadcast();
+        final ExpectedBroadcast b2 = expectProxyChangeAction();
         final Set<UidRange> newRanges = new HashSet<>(asList(
                 new UidRange(vpnRange.start, APP1_UID - 1),
                 new UidRange(APP1_UID + 1, vpnRange.stop)));
@@ -12257,20 +12778,20 @@
         b2.expectNoBroadcast(500);
 
         final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
-        final ExpectedBroadcast b3 = registerPacProxyBroadcast();
+        final ExpectedBroadcast b3 = expectProxyChangeAction();
         lp.setHttpProxy(testProxyInfo);
         mMockVpn.sendLinkProperties(lp);
         waitForIdle();
         // Proxy is set, so send a proxy broadcast.
         b3.expectBroadcast();
 
-        final ExpectedBroadcast b4 = registerPacProxyBroadcast();
+        final ExpectedBroadcast b4 = expectProxyChangeAction();
         mMockVpn.setUids(vpnRanges);
         waitForIdle();
         // Uid has changed and proxy is already set, so send a proxy broadcast.
         b4.expectBroadcast();
 
-        final ExpectedBroadcast b5 = registerPacProxyBroadcast();
+        final ExpectedBroadcast b5 = expectProxyChangeAction();
         // Proxy is removed, send a proxy broadcast.
         lp.setHttpProxy(null);
         mMockVpn.sendLinkProperties(lp);
@@ -12278,11 +12799,11 @@
         b5.expectBroadcast();
 
         // Proxy is added in WiFi(default network), setDefaultProxy will be called.
-        final LinkProperties wifiLp = mCm.getLinkProperties(mWiFiNetworkAgent.getNetwork());
+        final LinkProperties wifiLp = mCm.getLinkProperties(mWiFiAgent.getNetwork());
         assertNotNull(wifiLp);
         final ExpectedBroadcast b6 = expectProxyChangeAction(testProxyInfo);
         wifiLp.setHttpProxy(testProxyInfo);
-        mWiFiNetworkAgent.sendLinkProperties(wifiLp);
+        mWiFiAgent.sendLinkProperties(wifiLp);
         waitForIdle();
         b6.expectBroadcast();
     }
@@ -12290,8 +12811,8 @@
     @Test
     public void testProxyBroadcastWillBeSentWhenVpnHasProxyAndConnects() throws Exception {
         // Set up a WiFi network without proxy.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
         assertNull(mService.getProxyForNetwork(null));
         assertNull(mCm.getDefaultProxy());
 
@@ -12303,7 +12824,7 @@
         lp.setHttpProxy(testProxyInfo);
         final UidRange vpnRange = PRIMARY_UIDRANGE;
         final Set<UidRange> vpnRanges = Collections.singleton(vpnRange);
-        final ExpectedBroadcast b1 = registerPacProxyBroadcast();
+        final ExpectedBroadcast b1 = expectProxyChangeAction();
         mMockVpn.setOwnerAndAdminUid(VPN_UID);
         mMockVpn.registerAgent(false, vpnRanges, lp);
         // In any case, the proxy broadcast won't be sent before VPN goes into CONNECTED state.
@@ -12311,8 +12832,9 @@
         // proxy broadcast will get null.
         b1.expectNoBroadcast(500);
 
-        final ExpectedBroadcast b2 = registerPacProxyBroadcast();
-        mMockVpn.connect(true /* validated */, true /* hasInternet */, false /* isStrictMode */);
+        final ExpectedBroadcast b2 = expectProxyChangeAction();
+        mMockVpn.connect(true /* validated */, true /* hasInternet */,
+                false /* privateDnsProbeSent */);
         waitForIdle();
         assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID);
         // Vpn is connected with proxy, so the proxy broadcast will be sent to inform the apps to
@@ -12324,31 +12846,31 @@
     public void testProxyBroadcastWillBeSentWhenTheProxyOfNonDefaultNetworkHasChanged()
             throws Exception {
         // Set up a CELLULAR network without proxy.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
         assertNull(mService.getProxyForNetwork(null));
         assertNull(mCm.getDefaultProxy());
         // CELLULAR network should be the default network.
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Set up a WiFi network without proxy.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
         assertNull(mService.getProxyForNetwork(null));
         assertNull(mCm.getDefaultProxy());
         // WiFi network should be the default network.
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetwork());
         // CELLULAR network is not the default network.
-        assertNotEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertNotEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
 
         // CELLULAR network is not the system default network, but it might be a per-app default
         // network. The proxy broadcast should be sent once its proxy has changed.
         final LinkProperties cellularLp = new LinkProperties();
         cellularLp.setInterfaceName(MOBILE_IFNAME);
         final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
-        final ExpectedBroadcast b = registerPacProxyBroadcast();
+        final ExpectedBroadcast b = expectProxyChangeAction();
         cellularLp.setHttpProxy(testProxyInfo);
-        mCellNetworkAgent.sendLinkProperties(cellularLp);
+        mCellAgent.sendLinkProperties(cellularLp);
         b.expectBroadcast();
     }
 
@@ -12377,66 +12899,64 @@
                 .build();
         mCm.registerNetworkCallback(allNetworksRequest, allNetworksCb);
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true /* validated */);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true /* validated */);
 
-        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        allNetworksCb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        allNetworksCb.expectAvailableThenValidatedCallbacks(mCellAgent);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true /* validated */);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true /* validated */);
 
-        mDefaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        mDefaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
         // While the default callback doesn't see the network before it's validated, the listen
         // sees the network come up and validate later
-        allNetworksCb.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
-        allNetworksCb.expectLosing(mCellNetworkAgent);
-        allNetworksCb.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
-        allNetworksCb.expect(CallbackEntry.LOST, mCellNetworkAgent,
-                TEST_LINGER_DELAY_MS * 2);
+        allNetworksCb.expectAvailableCallbacksUnvalidated(mWiFiAgent);
+        allNetworksCb.expectLosing(mCellAgent);
+        allNetworksCb.expectCaps(mWiFiAgent, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
+        allNetworksCb.expect(LOST, mCellAgent, TEST_LINGER_DELAY_MS * 2);
 
         // The cell network has disconnected (see LOST above) because it was outscored and
         // had no requests (see setAlwaysOnNetworks(false) above)
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         final NetworkScore score = new NetworkScore.Builder().setLegacyInt(30).build();
-        mCellNetworkAgent.setScore(score);
-        mCellNetworkAgent.connect(false /* validated */);
+        mCellAgent.setScore(score);
+        mCellAgent.connect(false /* validated */);
 
         // The cell network gets torn down right away.
-        allNetworksCb.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
-        allNetworksCb.expect(CallbackEntry.LOST, mCellNetworkAgent,
-                TEST_NASCENT_DELAY_MS * 2);
+        allNetworksCb.expectAvailableCallbacksUnvalidated(mCellAgent);
+        allNetworksCb.expect(LOST, mCellAgent, TEST_NASCENT_DELAY_MS * 2);
         allNetworksCb.assertNoCallback();
 
         // Now create a cell network with KEEP_CONNECTED_FOR_HANDOVER and make sure it's
         // not disconnected immediately when outscored.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         final NetworkScore scoreKeepup = new NetworkScore.Builder().setLegacyInt(30)
                 .setKeepConnectedReason(KEEP_CONNECTED_FOR_HANDOVER).build();
-        mCellNetworkAgent.setScore(scoreKeepup);
-        mCellNetworkAgent.connect(true /* validated */);
+        mCellAgent.setScore(scoreKeepup);
+        mCellAgent.connect(true /* validated */);
 
-        allNetworksCb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        allNetworksCb.expectAvailableThenValidatedCallbacks(mCellAgent);
         mDefaultNetworkCallback.assertNoCallback();
 
-        mWiFiNetworkAgent.disconnect();
+        mWiFiAgent.disconnect();
 
-        allNetworksCb.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        mDefaultNetworkCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        allNetworksCb.expect(LOST, mWiFiAgent);
+        mDefaultNetworkCallback.expect(LOST, mWiFiAgent);
+        mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellAgent);
 
         // Reconnect a WiFi network and make sure the cell network is still not torn down.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true /* validated */);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true /* validated */);
 
-        allNetworksCb.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
-        mDefaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        allNetworksCb.expectAvailableThenValidatedCallbacks(mWiFiAgent);
+        mDefaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
 
         // Now remove the reason to keep connected and make sure the network lingers and is
         // torn down.
-        mCellNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(30).build());
-        allNetworksCb.expectLosing(mCellNetworkAgent, TEST_NASCENT_DELAY_MS * 2);
-        allNetworksCb.expect(CallbackEntry.LOST, mCellNetworkAgent, TEST_LINGER_DELAY_MS * 2);
+        mCellAgent.setScore(new NetworkScore.Builder().setLegacyInt(30).build());
+        allNetworksCb.expectLosing(mCellAgent, TEST_NASCENT_DELAY_MS * 2);
+        allNetworksCb.expect(LOST, mCellAgent, TEST_LINGER_DELAY_MS * 2);
         mDefaultNetworkCallback.assertNoCallback();
 
         mCm.unregisterNetworkCallback(allNetworksCb);
@@ -12454,21 +12974,21 @@
             mFilter = mock(QosFilter.class);
 
             // Ensure the network is disconnected before anything else occurs
-            assertNull(mCellNetworkAgent);
+            assertNull(mCellAgent);
 
-            mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-            mCellNetworkAgent.connect(true);
+            mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+            mCellAgent.connect(true);
 
             verifyActiveNetwork(TRANSPORT_CELLULAR);
             waitForIdle();
-            final Network network = mCellNetworkAgent.getNetwork();
+            final Network network = mCellAgent.getNetwork();
 
             final Pair<IQosCallback, IBinder> pair = createQosCallback();
             mCallback = pair.first;
 
             doReturn(network).when(mFilter).getNetwork();
             doReturn(QosCallbackException.EX_TYPE_FILTER_NONE).when(mFilter).validate();
-            mAgentWrapper = mCellNetworkAgent;
+            mAgentWrapper = mCellAgent;
         }
 
         void registerQosCallback(@NonNull final QosFilter filter,
@@ -13058,12 +13578,12 @@
             // Corresponds to a metered cellular network. Will be used for the default network.
             case TRANSPORT_CELLULAR:
                 if (!connectAgent) {
-                    mCellNetworkAgent.disconnect();
+                    mCellAgent.disconnect();
                     break;
                 }
-                mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-                mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
-                mCellNetworkAgent.connect(true);
+                mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+                mCellAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+                mCellAgent.connect(true);
                 break;
             // Corresponds to a restricted ethernet network with OEM_PAID/OEM_PRIVATE.
             case TRANSPORT_ETHERNET:
@@ -13076,12 +13596,12 @@
             // Corresponds to unmetered Wi-Fi.
             case TRANSPORT_WIFI:
                 if (!connectAgent) {
-                    mWiFiNetworkAgent.disconnect();
+                    mWiFiAgent.disconnect();
                     break;
                 }
-                mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-                mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
-                mWiFiNetworkAgent.connect(true);
+                mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+                mWiFiAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+                mWiFiAgent.connect(true);
                 break;
             default:
                 throw new AssertionError("Unsupported transport type passed in.");
@@ -13091,15 +13611,15 @@
     }
 
     private void startOemManagedNetwork(final boolean isOemPaid) throws Exception {
-        mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
-        mEthernetNetworkAgent.addCapability(
+        mEthernetAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
+        mEthernetAgent.addCapability(
                 isOemPaid ? NET_CAPABILITY_OEM_PAID : NET_CAPABILITY_OEM_PRIVATE);
-        mEthernetNetworkAgent.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
-        mEthernetNetworkAgent.connect(true);
+        mEthernetAgent.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+        mEthernetAgent.connect(true);
     }
 
     private void stopOemManagedNetwork() {
-        mEthernetNetworkAgent.disconnect();
+        mEthernetAgent.disconnect();
         waitForIdle();
     }
 
@@ -13332,7 +13852,7 @@
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
                 null,
-                mEthernetNetworkAgent.getNetwork());
+                mEthernetAgent.getNetwork());
 
         // Verify that the active network is correct
         verifyActiveNetwork(TRANSPORT_ETHERNET);
@@ -13353,16 +13873,16 @@
         assertTrue(mCm.isActiveNetworkMetered());
 
         // Connect to an unmetered restricted network that will only be available to the OEM pref.
-        mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
-        mEthernetNetworkAgent.addCapability(NET_CAPABILITY_OEM_PAID);
-        mEthernetNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
-        mEthernetNetworkAgent.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
-        mEthernetNetworkAgent.connect(true);
+        mEthernetAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
+        mEthernetAgent.addCapability(NET_CAPABILITY_OEM_PAID);
+        mEthernetAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        mEthernetAgent.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+        mEthernetAgent.connect(true);
         waitForIdle();
 
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
                 null,
-                mEthernetNetworkAgent.getNetwork());
+                mEthernetAgent.getNetwork());
 
         assertFalse(mCm.isActiveNetworkMetered());
         // default NCs will be unregistered in tearDown
@@ -13393,19 +13913,18 @@
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
                 null,
-                mEthernetNetworkAgent.getNetwork());
+                mEthernetAgent.getNetwork());
 
         // At this point with a restricted network used, the available callback should trigger.
-        defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
-        assertEquals(defaultNetworkCallback.getLastAvailableNetwork(),
-                mEthernetNetworkAgent.getNetwork());
+        defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mEthernetAgent);
+        assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mEthernetAgent.getNetwork());
         otherUidDefaultCallback.assertNoCallback();
 
         // Now bring down the default network which should trigger a LOST callback.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
 
         // At this point, with no network is available, the lost callback should trigger
-        defaultNetworkCallback.expect(CallbackEntry.LOST, mEthernetNetworkAgent);
+        defaultNetworkCallback.expect(LOST, mEthernetAgent);
         otherUidDefaultCallback.assertNoCallback();
 
         // Confirm we can unregister without issues.
@@ -13438,12 +13957,11 @@
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
                 null,
-                mEthernetNetworkAgent.getNetwork());
+                mEthernetAgent.getNetwork());
 
         // At this point with a restricted network used, the available callback should trigger
-        defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
-        assertEquals(defaultNetworkCallback.getLastAvailableNetwork(),
-                mEthernetNetworkAgent.getNetwork());
+        defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mEthernetAgent);
+        assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mEthernetAgent.getNetwork());
         otherUidDefaultCallback.assertNoCallback();
 
         // Now bring down the default network which should trigger a LOST callback.
@@ -13451,7 +13969,7 @@
         otherUidDefaultCallback.assertNoCallback();
 
         // At this point, with no network is available, the lost callback should trigger
-        defaultNetworkCallback.expect(CallbackEntry.LOST, mEthernetNetworkAgent);
+        defaultNetworkCallback.expect(LOST, mEthernetAgent);
         otherUidDefaultCallback.assertNoCallback();
 
         // Confirm we can unregister without issues.
@@ -13484,27 +14002,26 @@
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
                 null,
-                mEthernetNetworkAgent.getNetwork());
+                mEthernetAgent.getNetwork());
 
         // As this callback does not have access to the OEM_PAID network, it will not fire.
         defaultNetworkCallback.assertNoCallback();
         assertDefaultNetworkCapabilities(userId /* no networks */);
 
         // The other UID does have access, and gets a callback.
-        otherUidDefaultCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
+        otherUidDefaultCallback.expectAvailableThenValidatedCallbacks(mEthernetAgent);
 
         // Bring up unrestricted cellular. This should now satisfy the default network.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mCellNetworkAgent.getNetwork(),
-                mEthernetNetworkAgent.getNetwork());
+                mCellAgent.getNetwork(),
+                mEthernetAgent.getNetwork());
 
         // At this point with an unrestricted network used, the available callback should trigger
         // The other UID is unaffected and remains on the paid network.
-        defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        assertEquals(defaultNetworkCallback.getLastAvailableNetwork(),
-                mCellNetworkAgent.getNetwork());
-        assertDefaultNetworkCapabilities(userId, mCellNetworkAgent);
+        defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCellAgent.getNetwork());
+        assertDefaultNetworkCapabilities(userId, mCellAgent);
         otherUidDefaultCallback.assertNoCallback();
 
         // Now bring down the per-app network.
@@ -13513,13 +14030,13 @@
         // Since the callback didn't use the per-app network, only the other UID gets a callback.
         // Because the preference specifies no fallback, it does not switch to cellular.
         defaultNetworkCallback.assertNoCallback();
-        otherUidDefaultCallback.expect(CallbackEntry.LOST, mEthernetNetworkAgent);
+        otherUidDefaultCallback.expect(LOST, mEthernetAgent);
 
         // Now bring down the default network.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false);
 
         // As this callback was tracking the default, this should now trigger.
-        defaultNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        defaultNetworkCallback.expect(LOST, mCellAgent);
         otherUidDefaultCallback.assertNoCallback();
 
         // Confirm we can unregister without issues.
@@ -13672,22 +14189,22 @@
         // Bring up metered cellular. This will satisfy the fallback network.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
         verifySetOemNetworkPreferenceForPreference(uidRanges,
-                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                mCellAgent.getNetwork().netId, 1 /* times */,
                 OEM_PREF_ANY_NET_ID, 0 /* times */,
                 false /* shouldDestroyNetwork */);
 
         // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
         verifySetOemNetworkPreferenceForPreference(uidRanges,
-                mEthernetNetworkAgent.getNetwork().netId, 1 /* times */,
-                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                mEthernetAgent.getNetwork().netId, 1 /* times */,
+                mCellAgent.getNetwork().netId, 1 /* times */,
                 false /* shouldDestroyNetwork */);
 
         // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true);
         verifySetOemNetworkPreferenceForPreference(uidRanges,
-                mWiFiNetworkAgent.getNetwork().netId, 1 /* times */,
-                mEthernetNetworkAgent.getNetwork().netId, 1 /* times */,
+                mWiFiAgent.getNetwork().netId, 1 /* times */,
+                mEthernetAgent.getNetwork().netId, 1 /* times */,
                 false /* shouldDestroyNetwork */);
 
         // Disconnecting OEM_PAID should have no effect as it is lower in priority then unmetered.
@@ -13701,15 +14218,15 @@
         // Disconnecting unmetered should put PANS on lowest priority fallback request.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false);
         verifySetOemNetworkPreferenceForPreference(uidRanges,
-                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
-                mWiFiNetworkAgent.getNetwork().netId, 0 /* times */,
+                mCellAgent.getNetwork().netId, 1 /* times */,
+                mWiFiAgent.getNetwork().netId, 0 /* times */,
                 true /* shouldDestroyNetwork */);
 
         // Disconnecting the fallback network should result in no connectivity.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false);
         verifySetOemNetworkPreferenceForPreference(uidRanges,
                 OEM_PREF_ANY_NET_ID, 0 /* times */,
-                mCellNetworkAgent.getNetwork().netId, 0 /* times */,
+                mCellAgent.getNetwork().netId, 0 /* times */,
                 true /* shouldDestroyNetwork */);
     }
 
@@ -13747,22 +14264,22 @@
         // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
         verifySetOemNetworkPreferenceForPreference(uidRanges,
-                mEthernetNetworkAgent.getNetwork().netId, 1 /* times */,
+                mEthernetAgent.getNetwork().netId, 1 /* times */,
                 mService.mNoServiceNetwork.network.getNetId(), 1 /* times */,
                 false /* shouldDestroyNetwork */);
 
         // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true);
         verifySetOemNetworkPreferenceForPreference(uidRanges,
-                mWiFiNetworkAgent.getNetwork().netId, 1 /* times */,
-                mEthernetNetworkAgent.getNetwork().netId, 1 /* times */,
+                mWiFiAgent.getNetwork().netId, 1 /* times */,
+                mEthernetAgent.getNetwork().netId, 1 /* times */,
                 false /* shouldDestroyNetwork */);
 
         // Disconnecting unmetered should put PANS on OEM_PAID.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false);
         verifySetOemNetworkPreferenceForPreference(uidRanges,
-                mEthernetNetworkAgent.getNetwork().netId, 1 /* times */,
-                mWiFiNetworkAgent.getNetwork().netId, 0 /* times */,
+                mEthernetAgent.getNetwork().netId, 1 /* times */,
+                mWiFiAgent.getNetwork().netId, 0 /* times */,
                 true /* shouldDestroyNetwork */);
 
         // Disconnecting OEM_PAID should result in no connectivity.
@@ -13770,7 +14287,7 @@
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
         verifySetOemNetworkPreferenceForPreference(uidRanges,
                 mService.mNoServiceNetwork.network.getNetId(), 1 /* times */,
-                mEthernetNetworkAgent.getNetwork().netId, 0 /* times */,
+                mEthernetAgent.getNetwork().netId, 0 /* times */,
                 true /* shouldDestroyNetwork */);
     }
 
@@ -13814,7 +14331,7 @@
         // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
         verifySetOemNetworkPreferenceForPreference(uidRanges,
-                mEthernetNetworkAgent.getNetwork().netId, 1 /* times */,
+                mEthernetAgent.getNetwork().netId, 1 /* times */,
                 mService.mNoServiceNetwork.network.getNetId(), 1 /* times */,
                 false /* shouldDestroyNetwork */);
 
@@ -13822,7 +14339,7 @@
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
         verifySetOemNetworkPreferenceForPreference(uidRanges,
                 mService.mNoServiceNetwork.network.getNetId(), 1 /* times */,
-                mEthernetNetworkAgent.getNetwork().netId, 0 /* times */,
+                mEthernetAgent.getNetwork().netId, 0 /* times */,
                 true /* shouldDestroyNetwork */);
     }
 
@@ -13866,7 +14383,7 @@
         // Bring up ethernet with OEM_PRIVATE. This will satisfy NET_CAPABILITY_OEM_PRIVATE.
         startOemManagedNetwork(false);
         verifySetOemNetworkPreferenceForPreference(uidRanges,
-                mEthernetNetworkAgent.getNetwork().netId, 1 /* times */,
+                mEthernetAgent.getNetwork().netId, 1 /* times */,
                 mService.mNoServiceNetwork.network.getNetId(), 1 /* times */,
                 false /* shouldDestroyNetwork */);
 
@@ -13874,7 +14391,7 @@
         stopOemManagedNetwork();
         verifySetOemNetworkPreferenceForPreference(uidRanges,
                 mService.mNoServiceNetwork.network.getNetId(), 1 /* times */,
-                mEthernetNetworkAgent.getNetwork().netId, 0 /* times */,
+                mEthernetAgent.getNetwork().netId, 0 /* times */,
                 true /* shouldDestroyNetwork */);
     }
 
@@ -13907,7 +14424,7 @@
         // Test that we correctly add the expected values for multiple users.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
         verifySetOemNetworkPreferenceForPreference(uidRanges,
-                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                mCellAgent.getNetwork().netId, 1 /* times */,
                 OEM_PREF_ANY_NET_ID, 0 /* times */,
                 false /* shouldDestroyNetwork */);
 
@@ -13915,7 +14432,7 @@
         setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false);
         verifySetOemNetworkPreferenceForPreference(uidRanges,
                 OEM_PREF_ANY_NET_ID, 0 /* times */,
-                mCellNetworkAgent.getNetwork().netId, 0 /* times */,
+                mCellAgent.getNetwork().netId, 0 /* times */,
                 true /* shouldDestroyNetwork */);
     }
 
@@ -13950,7 +14467,7 @@
         // Test that we correctly add the expected values for multiple users.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
         verifySetOemNetworkPreferenceForPreference(uidRangesSingleUser,
-                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                mCellAgent.getNetwork().netId, 1 /* times */,
                 OEM_PREF_ANY_NET_ID, 0 /* times */,
                 false /* shouldDestroyNetwork */);
 
@@ -13963,8 +14480,8 @@
 
         // Test that we correctly add values for all users and remove for the single user.
         verifySetOemNetworkPreferenceForPreference(uidRangesBothUsers, uidRangesSingleUser,
-                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
-                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                mCellAgent.getNetwork().netId, 1 /* times */,
+                mCellAgent.getNetwork().netId, 1 /* times */,
                 false /* shouldDestroyNetwork */);
 
         // Send a broadcast indicating a user was removed.
@@ -13975,8 +14492,8 @@
 
         // Test that we correctly add values for the single user and remove for the all users.
         verifySetOemNetworkPreferenceForPreference(uidRangesSingleUser, uidRangesBothUsers,
-                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
-                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                mCellAgent.getNetwork().netId, 1 /* times */,
+                mCellAgent.getNetwork().netId, 1 /* times */,
                 false /* shouldDestroyNetwork */);
     }
 
@@ -14006,7 +14523,7 @@
         // Test that we correctly add the expected values for installed packages.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
         verifySetOemNetworkPreferenceForPreference(uidRangesSinglePackage,
-                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                mCellAgent.getNetwork().netId, 1 /* times */,
                 OEM_PREF_ANY_NET_ID, 0 /* times */,
                 false /* shouldDestroyNetwork */);
 
@@ -14022,8 +14539,8 @@
 
         // Test the single package is removed and the combined packages are added.
         verifySetOemNetworkPreferenceForPreference(uidRangesAllPackages, uidRangesSinglePackage,
-                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
-                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                mCellAgent.getNetwork().netId, 1 /* times */,
+                mCellAgent.getNetwork().netId, 1 /* times */,
                 false /* shouldDestroyNetwork */);
 
         // Set the system to no longer recognize the package to be installed
@@ -14036,8 +14553,8 @@
 
         // Test the combined packages are removed and the single package is added.
         verifySetOemNetworkPreferenceForPreference(uidRangesSinglePackage, uidRangesAllPackages,
-                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
-                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                mCellAgent.getNetwork().netId, 1 /* times */,
+                mCellAgent.getNetwork().netId, 1 /* times */,
                 false /* shouldDestroyNetwork */);
 
         // Set the system to change the installed package's uid
@@ -14053,8 +14570,8 @@
 
         // Test the original uid is removed and is replaced with the new uid.
         verifySetOemNetworkPreferenceForPreference(uidRangesReplacedPackage, uidRangesSinglePackage,
-                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
-                mCellNetworkAgent.getNetwork().netId, 1 /* times */,
+                mCellAgent.getNetwork().netId, 1 /* times */,
+                mCellAgent.getNetwork().netId, 1 /* times */,
                 false /* shouldDestroyNetwork */);
     }
 
@@ -14079,32 +14596,32 @@
         // Bring up metered cellular. This will satisfy the fallback network.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mCellNetworkAgent.getNetwork(),
-                mCellNetworkAgent.getNetwork());
+                mCellAgent.getNetwork(),
+                mCellAgent.getNetwork());
 
         // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mCellNetworkAgent.getNetwork(),
-                mEthernetNetworkAgent.getNetwork());
+                mCellAgent.getNetwork(),
+                mEthernetAgent.getNetwork());
 
         // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mWiFiNetworkAgent.getNetwork(),
-                mWiFiNetworkAgent.getNetwork());
+                mWiFiAgent.getNetwork(),
+                mWiFiAgent.getNetwork());
 
         // Disconnecting unmetered Wi-Fi will put the pref on OEM_PAID and fallback on cellular.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mCellNetworkAgent.getNetwork(),
-                mEthernetNetworkAgent.getNetwork());
+                mCellAgent.getNetwork(),
+                mEthernetAgent.getNetwork());
 
         // Disconnecting cellular should keep OEM network on OEM_PAID and fallback will be null.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
                 null,
-                mEthernetNetworkAgent.getNetwork());
+                mEthernetAgent.getNetwork());
 
         // Disconnecting OEM_PAID will put both on null as it is the last network.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
@@ -14152,8 +14669,8 @@
         oemPaidFactory.register();
         oemPaidFactory.expectRequestAdd(); // Because nobody satisfies the request
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
 
         // A network connected that satisfies the default internet request. For the OEM_PAID
         // preference, this is not as good as an OEM_PAID network, so even if the score of
@@ -14188,7 +14705,7 @@
         expectNoRequestChanged(internetFactory);
         internetFactory.assertRequestCountEquals(0);
 
-        mCellNetworkAgent.disconnect();
+        mCellAgent.disconnect();
         // The network satisfying the default internet request has disconnected, so the
         // internetFactory sees the default request again. However there is a network with OEM_PAID
         // connected, so the 2nd OEM_PAID req is already satisfied, so the oemPaidFactory doesn't
@@ -14199,8 +14716,8 @@
         internetFactory.assertRequestCountEquals(1);
 
         // Cell connects again, still with score 50. Back to the previous state.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
         expectNoRequestChanged(oemPaidFactory);
         oemPaidFactory.assertRequestCountEquals(1);
         internetFactory.expectRequestRemove();
@@ -14212,18 +14729,18 @@
                 wifiCallback);
 
         // Now WiFi connects and it's unmetered, but it's weaker than cell.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
-        mWiFiNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(30).setExiting(true)
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+        mWiFiAgent.setScore(new NetworkScore.Builder().setLegacyInt(30).setExiting(true)
                 .build()); // Not the best Internet network, but unmetered
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent.connect(true);
 
         // The OEM_PAID preference prefers an unmetered network to an OEM_PAID network, so
         // the oemPaidFactory can't beat wifi no matter how high its score.
         oemPaidFactory.expectRequestRemove();
         expectNoRequestChanged(internetFactory);
 
-        mCellNetworkAgent.disconnect();
+        mCellAgent.disconnect();
         // Now that the best internet network (cell, with its 50 score compared to 30 for WiFi
         // at this point), the default internet request is satisfied by a network worse than
         // the internetFactory announced, so it gets the request. However, there is still an
@@ -14254,32 +14771,32 @@
         // Bring up metered cellular. This will satisfy the fallback network but not the pref.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mCellNetworkAgent.getNetwork(),
+                mCellAgent.getNetwork(),
                 mService.mNoServiceNetwork.network());
 
         // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mCellNetworkAgent.getNetwork(),
-                mEthernetNetworkAgent.getNetwork());
+                mCellAgent.getNetwork(),
+                mEthernetAgent.getNetwork());
 
         // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mWiFiNetworkAgent.getNetwork(),
-                mWiFiNetworkAgent.getNetwork());
+                mWiFiAgent.getNetwork(),
+                mWiFiAgent.getNetwork());
 
         // Disconnecting unmetered Wi-Fi will put the OEM pref on OEM_PAID and fallback on cellular.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mCellNetworkAgent.getNetwork(),
-                mEthernetNetworkAgent.getNetwork());
+                mCellAgent.getNetwork(),
+                mEthernetAgent.getNetwork());
 
         // Disconnecting cellular should keep OEM network on OEM_PAID and fallback will be null.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
                 null,
-                mEthernetNetworkAgent.getNetwork());
+                mEthernetAgent.getNetwork());
 
         // Disconnecting OEM_PAID puts the fallback on null and the pref on the disconnected net.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
@@ -14312,32 +14829,32 @@
         // Bring up metered cellular. This will satisfy the fallback network.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mCellNetworkAgent.getNetwork(),
+                mCellAgent.getNetwork(),
                 mService.mNoServiceNetwork.network());
 
         // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mCellNetworkAgent.getNetwork(),
-                mEthernetNetworkAgent.getNetwork());
+                mCellAgent.getNetwork(),
+                mEthernetAgent.getNetwork());
 
         // Bring up unmetered Wi-Fi. The OEM network shouldn't change, the fallback will take Wi-Fi.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mWiFiNetworkAgent.getNetwork(),
-                mEthernetNetworkAgent.getNetwork());
+                mWiFiAgent.getNetwork(),
+                mEthernetAgent.getNetwork());
 
         // Disconnecting unmetered Wi-Fi shouldn't change the OEM network with fallback on cellular.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mCellNetworkAgent.getNetwork(),
-                mEthernetNetworkAgent.getNetwork());
+                mCellAgent.getNetwork(),
+                mEthernetAgent.getNetwork());
 
         // Disconnecting OEM_PAID will keep the fallback on cellular and nothing for OEM_PAID.
         // OEM_PAID_ONLY not supporting a fallback now uses the disconnected network.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mCellNetworkAgent.getNetwork(),
+                mCellAgent.getNetwork(),
                 mService.mNoServiceNetwork.network());
 
         // Disconnecting cellular will put the fallback on null and the pref on disconnected.
@@ -14371,32 +14888,32 @@
         // Bring up metered cellular. This will satisfy the fallback network.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mCellNetworkAgent.getNetwork(),
+                mCellAgent.getNetwork(),
                 mService.mNoServiceNetwork.network());
 
         // Bring up ethernet with OEM_PRIVATE. This will satisfy NET_CAPABILITY_OEM_PRIVATE.
         startOemManagedNetwork(false);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mCellNetworkAgent.getNetwork(),
-                mEthernetNetworkAgent.getNetwork());
+                mCellAgent.getNetwork(),
+                mEthernetAgent.getNetwork());
 
         // Bring up unmetered Wi-Fi. The OEM network shouldn't change, the fallback will take Wi-Fi.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mWiFiNetworkAgent.getNetwork(),
-                mEthernetNetworkAgent.getNetwork());
+                mWiFiAgent.getNetwork(),
+                mEthernetAgent.getNetwork());
 
         // Disconnecting unmetered Wi-Fi shouldn't change the OEM network with fallback on cellular.
         setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false);
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mCellNetworkAgent.getNetwork(),
-                mEthernetNetworkAgent.getNetwork());
+                mCellAgent.getNetwork(),
+                mEthernetAgent.getNetwork());
 
         // Disconnecting OEM_PRIVATE will keep the fallback on cellular.
         // OEM_PRIVATE_ONLY not supporting a fallback now uses to the disconnected network.
         stopOemManagedNetwork();
         verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize,
-                mCellNetworkAgent.getNetwork(),
+                mCellAgent.getNetwork(),
                 mService.mNoServiceNetwork.network());
 
         // Disconnecting cellular will put the fallback on null and pref on disconnected.
@@ -14417,14 +14934,14 @@
 
         setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
 
-        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
 
-        mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
-        mSystemDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc ->
-                nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
-        mDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc ->
-                nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+        mCellAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+        mSystemDefaultNetworkCallback.expectCaps(mCellAgent,
+                c -> c.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+        mDefaultNetworkCallback.expectCaps(mCellAgent,
+                c -> c.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
 
         // default callbacks will be unregistered in tearDown
     }
@@ -14478,29 +14995,29 @@
         final TestNetworkCallback cellCb = new TestNetworkCallback();
         mCm.requestNetwork(new NetworkRequest.Builder().addCapability(NET_CAPABILITY_MMS).build(),
                 cellCb);
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp, cellNcTemplate);
-        mCellNetworkAgent.connect(true);
-        cellCb.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp, cellNcTemplate);
+        mCellAgent.connect(true);
+        cellCb.expectAvailableCallbacksUnvalidated(mCellAgent);
         List<NetworkStateSnapshot> snapshots = mCm.getAllNetworkStateSnapshots();
         assertLength(1, snapshots);
 
         // Compose the expected cellular snapshot for verification.
         final NetworkCapabilities cellNc =
-                mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork());
+                mCm.getNetworkCapabilities(mCellAgent.getNetwork());
         final NetworkStateSnapshot cellSnapshot = new NetworkStateSnapshot(
-                mCellNetworkAgent.getNetwork(), cellNc, cellLp,
+                mCellAgent.getNetwork(), cellNc, cellLp,
                 null, ConnectivityManager.TYPE_MOBILE);
         assertEquals(cellSnapshot, snapshots.get(0));
 
         // Connect wifi and verify the snapshots.
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
         waitForIdle();
         // Compose the expected wifi snapshot for verification.
         final NetworkCapabilities wifiNc =
-                mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork());
+                mCm.getNetworkCapabilities(mWiFiAgent.getNetwork());
         final NetworkStateSnapshot wifiSnapshot = new NetworkStateSnapshot(
-                mWiFiNetworkAgent.getNetwork(), wifiNc, new LinkProperties(), null,
+                mWiFiAgent.getNetwork(), wifiNc, new LinkProperties(), null,
                 ConnectivityManager.TYPE_WIFI);
 
         snapshots = mCm.getAllNetworkStateSnapshots();
@@ -14508,33 +15025,33 @@
         assertContainsAll(snapshots, cellSnapshot, wifiSnapshot);
 
         // Set cellular as suspended, verify the snapshots will contain suspended networks.
-        mCellNetworkAgent.suspend();
+        mCellAgent.suspend();
         waitForIdle();
         final NetworkCapabilities cellSuspendedNc =
-                mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork());
+                mCm.getNetworkCapabilities(mCellAgent.getNetwork());
         assertFalse(cellSuspendedNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
         final NetworkStateSnapshot cellSuspendedSnapshot = new NetworkStateSnapshot(
-                mCellNetworkAgent.getNetwork(), cellSuspendedNc, cellLp,
+                mCellAgent.getNetwork(), cellSuspendedNc, cellLp,
                 null, ConnectivityManager.TYPE_MOBILE);
         snapshots = mCm.getAllNetworkStateSnapshots();
         assertLength(2, snapshots);
         assertContainsAll(snapshots, cellSuspendedSnapshot, wifiSnapshot);
 
         // Disconnect wifi, verify the snapshots contain only cellular.
-        mWiFiNetworkAgent.disconnect();
+        mWiFiAgent.disconnect();
         waitForIdle();
         snapshots = mCm.getAllNetworkStateSnapshots();
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetwork());
         assertLength(1, snapshots);
         assertEquals(cellSuspendedSnapshot, snapshots.get(0));
 
-        mCellNetworkAgent.resume();
+        mCellAgent.resume();
         waitForIdle();
         snapshots = mCm.getAllNetworkStateSnapshots();
         assertLength(1, snapshots);
         assertEquals(cellSnapshot, snapshots.get(0));
 
-        mCellNetworkAgent.disconnect();
+        mCellAgent.disconnect();
         waitForIdle();
         verifyNoNetwork();
         mCm.unregisterNetworkCallback(cellCb);
@@ -14605,35 +15122,35 @@
                 new NetworkRequest.Builder().addCapability(NET_CAPABILITY_TRUSTED).build(),
                 bestMatchingCb, mCsHandlerThread.getThreadHandler());
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        bestMatchingCb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        bestMatchingCb.expectAvailableThenValidatedCallbacks(mCellAgent);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
-        bestMatchingCb.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
+        bestMatchingCb.expectAvailableDoubleValidatedCallbacks(mWiFiAgent);
 
         // Change something on cellular to trigger capabilities changed, since the callback
         // only cares about the best network, verify it received nothing from cellular.
-        mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+        mCellAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
         bestMatchingCb.assertNoCallback();
 
         // Make cellular the best network again, verify the callback now tracks cellular.
-        mWiFiNetworkAgent.adjustScore(-50);
-        bestMatchingCb.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        mWiFiAgent.adjustScore(-50);
+        bestMatchingCb.expectAvailableCallbacksValidated(mCellAgent);
 
         // Make cellular temporary non-trusted, which will not satisfying the request.
         // Verify the callback switch from/to the other network accordingly.
-        mCellNetworkAgent.removeCapability(NET_CAPABILITY_TRUSTED);
-        bestMatchingCb.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
-        mCellNetworkAgent.addCapability(NET_CAPABILITY_TRUSTED);
-        bestMatchingCb.expectAvailableDoubleValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent.removeCapability(NET_CAPABILITY_TRUSTED);
+        bestMatchingCb.expectAvailableCallbacksValidated(mWiFiAgent);
+        mCellAgent.addCapability(NET_CAPABILITY_TRUSTED);
+        bestMatchingCb.expectAvailableDoubleValidatedCallbacks(mCellAgent);
 
         // Verify the callback doesn't care about wifi disconnect.
-        mWiFiNetworkAgent.disconnect();
+        mWiFiAgent.disconnect();
         bestMatchingCb.assertNoCallback();
-        mCellNetworkAgent.disconnect();
-        bestMatchingCb.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        mCellAgent.disconnect();
+        bestMatchingCb.expect(LOST, mCellAgent);
     }
 
     private UidRangeParcel[] uidRangeFor(final UserHandle handle) {
@@ -14728,25 +15245,32 @@
             UserHandle testHandle,
             TestNetworkCallback profileDefaultNetworkCallback,
             TestNetworkCallback disAllowProfileDefaultNetworkCallback) throws Exception {
-        final InOrder inOrder = inOrder(mMockNetd);
+        final InOrder inOrder = inOrder(mMockNetd, mMockDnsResolver);
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
 
-        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        profileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        profileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
         if (disAllowProfileDefaultNetworkCallback != null) {
-            disAllowProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(
-                    mCellNetworkAgent);
+            disAllowProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
         }
         inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
-                mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
+                mCellAgent.getNetwork().netId, INetd.PERMISSION_NONE));
 
         final TestNetworkAgentWrapper workAgent =
                 makeEnterpriseNetworkAgent(profileNetworkPreference.getPreferenceEnterpriseId());
+        if (mService.shouldCreateNetworksImmediately()) {
+            expectNativeNetworkCreated(workAgent.getNetwork().netId, INetd.PERMISSION_SYSTEM,
+                    null /* iface */, inOrder);
+        }
         if (connectWorkProfileAgentAhead) {
             workAgent.connect(false);
+            if (!mService.shouldCreateNetworksImmediately()) {
+                expectNativeNetworkCreated(workAgent.getNetwork().netId, INetd.PERMISSION_SYSTEM,
+                        null /* iface */, inOrder);
+            }
         }
 
         final TestOnCompleteListener listener = new TestOnCompleteListener();
@@ -14768,7 +15292,7 @@
             // system default is not handled specially, the rules are always active as long as
             // a preference is set.
             inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
-                    mCellNetworkAgent.getNetwork().netId,
+                    mCellAgent.getNetwork().netId,
                     uidRangeFor(testHandle, profileNetworkPreference),
                     PREFERENCE_ORDER_PROFILE));
         }
@@ -14778,7 +15302,7 @@
         if (allowFallback && !connectWorkProfileAgentAhead) {
             assertNoCallbacks(profileDefaultNetworkCallback);
         } else if (!connectWorkProfileAgentAhead) {
-            profileDefaultNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+            profileDefaultNetworkCallback.expect(LOST, mCellAgent);
             if (disAllowProfileDefaultNetworkCallback != null) {
                 assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
             }
@@ -14786,6 +15310,11 @@
 
         if (!connectWorkProfileAgentAhead) {
             workAgent.connect(false);
+            if (!mService.shouldCreateNetworksImmediately()) {
+                inOrder.verify(mMockNetd).networkCreate(
+                        nativeNetworkConfigPhysical(workAgent.getNetwork().netId,
+                                INetd.PERMISSION_SYSTEM));
+            }
         }
 
         profileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent);
@@ -14794,8 +15323,6 @@
         }
         mSystemDefaultNetworkCallback.assertNoCallback();
         mDefaultNetworkCallback.assertNoCallback();
-        inOrder.verify(mMockNetd).networkCreate(
-                nativeNetworkConfigPhysical(workAgent.getNetwork().netId, INetd.PERMISSION_SYSTEM));
         inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
                 workAgent.getNetwork().netId,
                 uidRangeFor(testHandle, profileNetworkPreference),
@@ -14803,29 +15330,28 @@
 
         if (allowFallback && !connectWorkProfileAgentAhead) {
             inOrder.verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
-                    mCellNetworkAgent.getNetwork().netId,
+                    mCellAgent.getNetwork().netId,
                     uidRangeFor(testHandle, profileNetworkPreference),
                     PREFERENCE_ORDER_PROFILE));
         }
 
         // Make sure changes to the work agent send callbacks to the app in the work profile, but
         // not to the other apps.
-        workAgent.setNetworkValid(true /* isStrictMode */);
+        workAgent.setNetworkValid(true /* privateDnsProbeSent */);
         workAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
-        profileDefaultNetworkCallback.expectCapabilitiesThat(workAgent,
-                nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED)
-                        && nc.hasCapability(NET_CAPABILITY_ENTERPRISE)
-                        && nc.hasEnterpriseId(
-                                profileNetworkPreference.getPreferenceEnterpriseId())
-                        && nc.getEnterpriseIds().length == 1);
+        profileDefaultNetworkCallback.expectCaps(workAgent,
+                c -> c.hasCapability(NET_CAPABILITY_VALIDATED)
+                        && c.hasCapability(NET_CAPABILITY_ENTERPRISE)
+                        && c.hasEnterpriseId(profileNetworkPreference.getPreferenceEnterpriseId())
+                        && c.getEnterpriseIds().length == 1);
         if (disAllowProfileDefaultNetworkCallback != null) {
             assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
         }
         assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
 
         workAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
-        profileDefaultNetworkCallback.expectCapabilitiesThat(workAgent, nc ->
-                nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+        profileDefaultNetworkCallback.expectCaps(workAgent,
+                c -> c.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
         if (disAllowProfileDefaultNetworkCallback != null) {
             assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
         }
@@ -14833,49 +15359,47 @@
 
         // Conversely, change a capability on the system-wide default network and make sure
         // that only the apps outside of the work profile receive the callbacks.
-        mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
-        mSystemDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc ->
-                nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
-        mDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc ->
-                nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+        mCellAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+        mSystemDefaultNetworkCallback.expectCaps(mCellAgent,
+                c -> c.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+        mDefaultNetworkCallback.expectCaps(mCellAgent,
+                c -> c.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
         if (disAllowProfileDefaultNetworkCallback != null) {
-            disAllowProfileDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc ->
-                    nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
+            disAllowProfileDefaultNetworkCallback.expectCaps(mCellAgent,
+                    c -> c.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
         }
         profileDefaultNetworkCallback.assertNoCallback();
 
         // Disconnect and reconnect the system-wide default network and make sure that the
         // apps on this network see the appropriate callbacks, and the app on the work profile
         // doesn't because it continues to use the enterprise network.
-        mCellNetworkAgent.disconnect();
-        mSystemDefaultNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
-        mDefaultNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        mCellAgent.disconnect();
+        mSystemDefaultNetworkCallback.expect(LOST, mCellAgent);
+        mDefaultNetworkCallback.expect(LOST, mCellAgent);
         if (disAllowProfileDefaultNetworkCallback != null) {
-            disAllowProfileDefaultNetworkCallback.expect(
-                    CallbackEntry.LOST, mCellNetworkAgent);
+            disAllowProfileDefaultNetworkCallback.expect(LOST, mCellAgent);
         }
         profileDefaultNetworkCallback.assertNoCallback();
-        inOrder.verify(mMockNetd).networkDestroy(mCellNetworkAgent.getNetwork().netId);
+        inOrder.verify(mMockNetd).networkDestroy(mCellAgent.getNetwork().netId);
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
         if (disAllowProfileDefaultNetworkCallback != null) {
-            disAllowProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(
-                    mCellNetworkAgent);
+            disAllowProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
 
         }
         profileDefaultNetworkCallback.assertNoCallback();
         inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
-                mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
+                mCellAgent.getNetwork().netId, INetd.PERMISSION_NONE));
 
         // When the agent disconnects, test that the app on the work profile falls back to the
         // default network.
         workAgent.disconnect();
-        profileDefaultNetworkCallback.expect(CallbackEntry.LOST, workAgent);
+        profileDefaultNetworkCallback.expect(LOST, workAgent);
         if (allowFallback) {
-            profileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+            profileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellAgent);
             if (disAllowProfileDefaultNetworkCallback != null) {
                 assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
             }
@@ -14883,27 +15407,26 @@
         assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
         if (allowFallback) {
             inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
-                    mCellNetworkAgent.getNetwork().netId,
+                    mCellAgent.getNetwork().netId,
                     uidRangeFor(testHandle, profileNetworkPreference),
                     PREFERENCE_ORDER_PROFILE));
         }
         inOrder.verify(mMockNetd).networkDestroy(workAgent.getNetwork().netId);
 
-        mCellNetworkAgent.disconnect();
-        mSystemDefaultNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
-        mDefaultNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        mCellAgent.disconnect();
+        mSystemDefaultNetworkCallback.expect(LOST, mCellAgent);
+        mDefaultNetworkCallback.expect(LOST, mCellAgent);
         if (disAllowProfileDefaultNetworkCallback != null) {
-            disAllowProfileDefaultNetworkCallback.expect(
-                    CallbackEntry.LOST, mCellNetworkAgent);
+            disAllowProfileDefaultNetworkCallback.expect(LOST, mCellAgent);
         }
         if (allowFallback) {
-            profileDefaultNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
+            profileDefaultNetworkCallback.expect(LOST, mCellAgent);
         }
 
         // Waiting for the handler to be idle before checking for networkDestroy is necessary
         // here because ConnectivityService calls onLost before the network is fully torn down.
         waitForIdle();
-        inOrder.verify(mMockNetd).networkDestroy(mCellNetworkAgent.getNetwork().netId);
+        inOrder.verify(mMockNetd).networkDestroy(mCellAgent.getNetwork().netId);
 
         // If the control comes here, callbacks seem to behave correctly in the presence of
         // a default network when the enterprise network goes up and down. Now, make sure they
@@ -14923,14 +15446,13 @@
                 workAgent2.getNetwork().netId,
                 uidRangeFor(testHandle, profileNetworkPreference), PREFERENCE_ORDER_PROFILE));
 
-        workAgent2.setNetworkValid(true /* isStrictMode */);
+        workAgent2.setNetworkValid(true /* privateDnsProbeSent */);
         workAgent2.mNetworkMonitor.forceReevaluation(Process.myUid());
-        profileDefaultNetworkCallback.expectCapabilitiesThat(workAgent2,
-                nc -> nc.hasCapability(NET_CAPABILITY_ENTERPRISE)
-                        && !nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
-                        && nc.hasEnterpriseId(
-                        profileNetworkPreference.getPreferenceEnterpriseId())
-                        && nc.getEnterpriseIds().length == 1);
+        profileDefaultNetworkCallback.expectCaps(workAgent2,
+                c -> c.hasCapability(NET_CAPABILITY_ENTERPRISE)
+                        && !c.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                        && c.hasEnterpriseId(profileNetworkPreference.getPreferenceEnterpriseId())
+                        && c.getEnterpriseIds().length == 1);
         if (disAllowProfileDefaultNetworkCallback != null) {
             assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
         }
@@ -14940,7 +15462,7 @@
         // When the agent disconnects, test that the app on the work profile fall back to the
         // default network.
         workAgent2.disconnect();
-        profileDefaultNetworkCallback.expect(CallbackEntry.LOST, workAgent2);
+        profileDefaultNetworkCallback.expect(LOST, workAgent2);
         if (disAllowProfileDefaultNetworkCallback != null) {
             assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
         }
@@ -15286,23 +15808,23 @@
         registerDefaultNetworkCallbackAsUid(appCb3, testWorkProfileAppUid3);
 
         // Connect both a regular cell agent and an enterprise network first.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
 
         final TestNetworkAgentWrapper workAgent1 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_1);
         final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_2);
         workAgent1.connect(true);
         workAgent2.connect(true);
 
-        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
 
-        appCb1.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        appCb2.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        appCb3.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        appCb1.expectAvailableThenValidatedCallbacks(mCellAgent);
+        appCb2.expectAvailableThenValidatedCallbacks(mCellAgent);
+        appCb3.expectAvailableThenValidatedCallbacks(mCellAgent);
 
         verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
-                mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
+                mCellAgent.getNetwork().netId, INetd.PERMISSION_NONE));
         verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
                 workAgent1.getNetwork().netId, INetd.PERMISSION_SYSTEM));
         verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
@@ -15369,8 +15891,8 @@
 
         assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
         appCb3.expectAvailableCallbacksValidated(workAgent1);
-        appCb2.expectAvailableCallbacksValidated(mCellNetworkAgent);
-        appCb1.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        appCb2.expectAvailableCallbacksValidated(mCellAgent);
+        appCb1.expectAvailableCallbacksValidated(mCellAgent);
 
         // Set the preferences for testHandle to default.
         ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
@@ -15387,9 +15909,9 @@
                 PREFERENCE_ORDER_PROFILE));
 
         assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, appCb1, appCb2);
-        appCb3.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        appCb3.expectAvailableCallbacksValidated(mCellAgent);
         workAgent2.disconnect();
-        mCellNetworkAgent.disconnect();
+        mCellAgent.disconnect();
 
         mCm.unregisterNetworkCallback(appCb1);
         mCm.unregisterNetworkCallback(appCb2);
@@ -15434,8 +15956,8 @@
         registerDefaultNetworkCallbackAsUid(appCb5, testWorkProfileAppUid5);
 
         // Connect both a regular cell agent and an enterprise network first.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
 
         final TestNetworkAgentWrapper workAgent1 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_1);
         final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_2);
@@ -15449,16 +15971,16 @@
         workAgent4.connect(true);
         workAgent5.connect(true);
 
-        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        appCb1.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        appCb2.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        appCb3.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        appCb4.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        appCb5.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        appCb1.expectAvailableThenValidatedCallbacks(mCellAgent);
+        appCb2.expectAvailableThenValidatedCallbacks(mCellAgent);
+        appCb3.expectAvailableThenValidatedCallbacks(mCellAgent);
+        appCb4.expectAvailableThenValidatedCallbacks(mCellAgent);
+        appCb5.expectAvailableThenValidatedCallbacks(mCellAgent);
 
         verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
-                mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
+                mCellAgent.getNetwork().netId, INetd.PERMISSION_NONE));
         verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
                 workAgent1.getNetwork().netId, INetd.PERMISSION_SYSTEM));
         verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
@@ -15550,36 +16072,36 @@
         workAgent4.disconnect();
         workAgent5.disconnect();
 
-        appCb1.expect(CallbackEntry.LOST, workAgent1);
-        appCb2.expect(CallbackEntry.LOST, workAgent2);
-        appCb3.expect(CallbackEntry.LOST, workAgent3);
-        appCb4.expect(CallbackEntry.LOST, workAgent4);
-        appCb5.expect(CallbackEntry.LOST, workAgent5);
+        appCb1.expect(LOST, workAgent1);
+        appCb2.expect(LOST, workAgent2);
+        appCb3.expect(LOST, workAgent3);
+        appCb4.expect(LOST, workAgent4);
+        appCb5.expect(LOST, workAgent5);
 
-        appCb1.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        appCb1.expectAvailableCallbacksValidated(mCellAgent);
         appCb2.assertNoCallback();
-        appCb3.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        appCb3.expectAvailableCallbacksValidated(mCellAgent);
         appCb4.assertNoCallback();
-        appCb5.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        appCb5.expectAvailableCallbacksValidated(mCellAgent);
 
         verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
-                mCellNetworkAgent.getNetwork().netId,
+                mCellAgent.getNetwork().netId,
                 uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
                 PREFERENCE_ORDER_PROFILE));
         verify(mMockNetd, never()).networkAddUidRangesParcel(new NativeUidRangeConfig(
-                mCellNetworkAgent.getNetwork().netId,
+                mCellAgent.getNetwork().netId,
                 uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
                 PREFERENCE_ORDER_PROFILE));
         verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
-                mCellNetworkAgent.getNetwork().netId,
+                mCellAgent.getNetwork().netId,
                 uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
                 PREFERENCE_ORDER_PROFILE));
         verify(mMockNetd, never()).networkAddUidRangesParcel(new NativeUidRangeConfig(
-                mCellNetworkAgent.getNetwork().netId,
+                mCellAgent.getNetwork().netId,
                 uidRangeFor(testHandle, profileNetworkPreferenceBuilder4.build()),
                 PREFERENCE_ORDER_PROFILE));
         verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
-                mCellNetworkAgent.getNetwork().netId,
+                mCellAgent.getNetwork().netId,
                 uidRangeFor(testHandle, profileNetworkPreferenceBuilder5.build()),
                 PREFERENCE_ORDER_PROFILE));
 
@@ -15597,9 +16119,9 @@
         listener.expectOnComplete();
         assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, appCb1, appCb3,
                 appCb5);
-        appCb2.expectAvailableCallbacksValidated(mCellNetworkAgent);
-        appCb4.expectAvailableCallbacksValidated(mCellNetworkAgent);
-        mCellNetworkAgent.disconnect();
+        appCb2.expectAvailableCallbacksValidated(mCellAgent);
+        appCb4.expectAvailableCallbacksValidated(mCellAgent);
+        mCellAgent.disconnect();
 
         mCm.unregisterNetworkCallback(appCb1);
         mCm.unregisterNetworkCallback(appCb2);
@@ -15619,8 +16141,8 @@
         final UserHandle testHandle = setupEnterpriseNetwork();
 
         // Connect both a regular cell agent and an enterprise network first.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
 
         final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent();
         workAgent.connect(true);
@@ -15630,27 +16152,27 @@
                 r -> r.run(), listener);
         listener.expectOnComplete();
         inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
-                mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
+                mCellAgent.getNetwork().netId, INetd.PERMISSION_NONE));
         inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
                 workAgent.getNetwork().netId, uidRangeFor(testHandle), PREFERENCE_ORDER_PROFILE));
 
         registerDefaultNetworkCallbacks();
 
-        mSystemDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
-        mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        mSystemDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellAgent);
+        mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellAgent);
         mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(workAgent);
 
         mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_DEFAULT,
                 r -> r.run(), listener);
         listener.expectOnComplete();
 
-        mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellAgent);
         assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
         inOrder.verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
                 workAgent.getNetwork().netId, uidRangeFor(testHandle), PREFERENCE_ORDER_PROFILE));
 
         workAgent.disconnect();
-        mCellNetworkAgent.disconnect();
+        mCellAgent.disconnect();
 
         // Callbacks will be unregistered by tearDown()
     }
@@ -15672,18 +16194,18 @@
         registerDefaultNetworkCallbackAsUid(app4Cb, testWorkProfileAppUid4);
 
         // Connect both a regular cell agent and an enterprise network first.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
 
         final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent();
         workAgent.connect(true);
 
-        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        app4Cb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        app4Cb.expectAvailableThenValidatedCallbacks(mCellAgent);
         inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
-                mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
+                mCellAgent.getNetwork().netId, INetd.PERMISSION_NONE));
         inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
                 workAgent.getNetwork().netId, INetd.PERMISSION_SYSTEM));
 
@@ -15714,12 +16236,12 @@
         inOrder.verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
                 workAgent.getNetwork().netId, uidRangeFor(testHandle2), PREFERENCE_ORDER_PROFILE));
 
-        mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellAgent);
         assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback,
                 app4Cb);
 
         workAgent.disconnect();
-        mCellNetworkAgent.disconnect();
+        mCellAgent.disconnect();
 
         mCm.unregisterNetworkCallback(app4Cb);
         // Other callbacks will be unregistered by tearDown()
@@ -15730,17 +16252,17 @@
         final InOrder inOrder = inOrder(mMockNetd);
         final UserHandle testHandle = setupEnterpriseNetwork();
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
 
         final TestOnCompleteListener listener = new TestOnCompleteListener();
         mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
                 r -> r.run(), listener);
         listener.expectOnComplete();
         inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
-                mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
+                mCellAgent.getNetwork().netId, INetd.PERMISSION_NONE));
         inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
-                mCellNetworkAgent.getNetwork().netId, uidRangeFor(testHandle),
+                mCellAgent.getNetwork().netId, uidRangeFor(testHandle),
                 PREFERENCE_ORDER_PROFILE));
 
         final Intent removedIntent = new Intent(ACTION_USER_REMOVED);
@@ -15748,10 +16270,256 @@
         processBroadcast(removedIntent);
 
         inOrder.verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
-                mCellNetworkAgent.getNetwork().netId, uidRangeFor(testHandle),
+                mCellAgent.getNetwork().netId, uidRangeFor(testHandle),
                 PREFERENCE_ORDER_PROFILE));
     }
 
+    @Test
+    public void testProfileNetworkPreferenceBlocking_addUser() throws Exception {
+        final InOrder inOrder = inOrder(mMockNetd);
+        doReturn(asList(PRIMARY_USER_HANDLE)).when(mUserManager).getUserHandles(anyBoolean());
+
+        // Only one network
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+
+        // Verify uid ranges 0~99999 are allowed
+        final ArraySet<UidRange> allowedRanges = new ArraySet<>();
+        allowedRanges.add(PRIMARY_UIDRANGE);
+        final NativeUidRangeConfig config1User = new NativeUidRangeConfig(
+                mCellAgent.getNetwork().netId,
+                toUidRangeStableParcels(allowedRanges),
+                0 /* subPriority */);
+        if (mDeps.isAtLeastU()) {
+            inOrder.verify(mMockNetd).setNetworkAllowlist(new NativeUidRangeConfig[]{config1User});
+        } else {
+            inOrder.verify(mMockNetd, never()).setNetworkAllowlist(any());
+        }
+
+        doReturn(asList(PRIMARY_USER_HANDLE, SECONDARY_USER_HANDLE))
+                .when(mUserManager).getUserHandles(anyBoolean());
+        final Intent addedIntent = new Intent(ACTION_USER_ADDED);
+        addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(SECONDARY_USER));
+        processBroadcast(addedIntent);
+
+        // Make sure the allow list has been updated.
+        allowedRanges.add(UidRange.createForUser(SECONDARY_USER_HANDLE));
+        final NativeUidRangeConfig config2Users = new NativeUidRangeConfig(
+                mCellAgent.getNetwork().netId,
+                toUidRangeStableParcels(allowedRanges),
+                0 /* subPriority */);
+        if (mDeps.isAtLeastU()) {
+            inOrder.verify(mMockNetd).setNetworkAllowlist(new NativeUidRangeConfig[]{config2Users});
+        } else {
+            inOrder.verify(mMockNetd, never()).setNetworkAllowlist(any());
+        }
+    }
+
+    @Test
+    public void testProfileNetworkPreferenceBlocking_changePreference() throws Exception {
+        final InOrder inOrder = inOrder(mMockNetd);
+        final UserHandle testHandle = setupEnterpriseNetwork();
+        doReturn(asList(PRIMARY_USER_HANDLE, testHandle))
+                .when(mUserManager).getUserHandles(anyBoolean());
+
+        // Start with 1 default network and 1 enterprise network, both networks should
+        // not be restricted since the blocking preference is not set yet.
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+
+        // Verify uid ranges 0~99999, 200000~299999 are all allowed for cellular.
+        final UidRange profileUidRange =
+                UidRange.createForUser(UserHandle.of(TEST_WORK_PROFILE_USER_ID));
+        ArraySet<UidRange> allowedAllUidRanges = new ArraySet<>();
+        allowedAllUidRanges.add(PRIMARY_UIDRANGE);
+        allowedAllUidRanges.add(profileUidRange);
+        final UidRangeParcel[] allowAllUidRangesParcel = toUidRangeStableParcels(
+                allowedAllUidRanges);
+        final NativeUidRangeConfig cellAllAllowedConfig = new NativeUidRangeConfig(
+                mCellAgent.getNetwork().netId,
+                allowAllUidRangesParcel,
+                0 /* subPriority */);
+        if (mDeps.isAtLeastU()) {
+            inOrder.verify(mMockNetd).setNetworkAllowlist(
+                    new NativeUidRangeConfig[]{cellAllAllowedConfig});
+        } else {
+            inOrder.verify(mMockNetd, never()).setNetworkAllowlist(any());
+        }
+
+        // Verify the same uid ranges are also applied for enterprise network.
+        final TestNetworkAgentWrapper enterpriseAgent = makeEnterpriseNetworkAgent(
+                NET_ENTERPRISE_ID_1);
+        enterpriseAgent.connect(true);
+        final NativeUidRangeConfig enterpriseAllAllowedConfig = new NativeUidRangeConfig(
+                enterpriseAgent.getNetwork().netId,
+                allowAllUidRangesParcel,
+                0 /* subPriority */);
+        // Network agents are stored in an ArraySet which does not guarantee the order and
+        // making the order of the list undeterministic. Thus, verify this in order insensitive way.
+        final ArgumentCaptor<NativeUidRangeConfig[]> configsCaptor = ArgumentCaptor.forClass(
+                NativeUidRangeConfig[].class);
+        if (mDeps.isAtLeastU()) {
+            inOrder.verify(mMockNetd).setNetworkAllowlist(configsCaptor.capture());
+            assertContainsAll(List.of(configsCaptor.getValue()),
+                    List.of(cellAllAllowedConfig, enterpriseAllAllowedConfig));
+        } else {
+            inOrder.verify(mMockNetd, never()).setNetworkAllowlist(any());
+        }
+
+        // Setup profile preference which only applies to test app uid on the managed profile.
+        ProfileNetworkPreference.Builder prefBuilder = new ProfileNetworkPreference.Builder();
+        prefBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING)
+                .setIncludedUids(new int[]{testHandle.getUid(TEST_WORK_PROFILE_APP_UID)})
+                .setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+        final TestOnCompleteListener listener = new TestOnCompleteListener();
+        mCm.setProfileNetworkPreferences(testHandle,
+                List.of(prefBuilder.build()),
+                r -> r.run(), listener);
+        listener.expectOnComplete();
+
+        // Verify Netd is called for the preferences changed.
+        // Cell: 0~99999, 200000~TEST_APP_UID-1, TEST_APP_UID+1~299999
+        // Enterprise: 0~99999, 200000~299999
+        final ArraySet<UidRange> excludeAppRanges = new ArraySet<>();
+        excludeAppRanges.add(PRIMARY_UIDRANGE);
+        excludeAppRanges.addAll(UidRangeUtils.removeRangeSetFromUidRange(
+                profileUidRange,
+                new ArraySet(new UidRange[]{
+                        (new UidRange(TEST_WORK_PROFILE_APP_UID, TEST_WORK_PROFILE_APP_UID))})
+        ));
+        final UidRangeParcel[] excludeAppRangesParcel = toUidRangeStableParcels(excludeAppRanges);
+        final NativeUidRangeConfig cellExcludeAppConfig = new NativeUidRangeConfig(
+                mCellAgent.getNetwork().netId,
+                excludeAppRangesParcel,
+                0 /* subPriority */);
+        if (mDeps.isAtLeastU()) {
+            inOrder.verify(mMockNetd).setNetworkAllowlist(configsCaptor.capture());
+            assertContainsAll(List.of(configsCaptor.getValue()),
+                    List.of(cellExcludeAppConfig, enterpriseAllAllowedConfig));
+        } else {
+            inOrder.verify(mMockNetd, never()).setNetworkAllowlist(any());
+        }
+
+        // Verify unset by giving all allowed set for all users when the preference got removed.
+        mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
+                r -> r.run(), listener);
+        listener.expectOnComplete();
+        if (mDeps.isAtLeastU()) {
+            inOrder.verify(mMockNetd).setNetworkAllowlist(configsCaptor.capture());
+            assertContainsAll(List.of(configsCaptor.getValue()),
+                    List.of(cellAllAllowedConfig, enterpriseAllAllowedConfig));
+        } else {
+            inOrder.verify(mMockNetd, never()).setNetworkAllowlist(any());
+        }
+
+        // Verify issuing with cellular set only when a network with enterprise capability
+        // disconnects.
+        enterpriseAgent.disconnect();
+        waitForIdle();
+        if (mDeps.isAtLeastU()) {
+            inOrder.verify(mMockNetd).setNetworkAllowlist(
+                    new NativeUidRangeConfig[]{cellAllAllowedConfig});
+        } else {
+            inOrder.verify(mMockNetd, never()).setNetworkAllowlist(any());
+        }
+    }
+
+    @Test
+    public void testProfileNetworkPreferenceBlocking_networkChanges() throws Exception {
+        final InOrder inOrder = inOrder(mMockNetd);
+        final UserHandle testHandle = setupEnterpriseNetwork();
+        doReturn(asList(PRIMARY_USER_HANDLE, testHandle))
+                .when(mUserManager).getUserHandles(anyBoolean());
+
+        // Setup profile preference which only applies to test app uid on the managed profile.
+        ProfileNetworkPreference.Builder prefBuilder = new ProfileNetworkPreference.Builder();
+        prefBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING)
+                .setIncludedUids(new int[]{testHandle.getUid(TEST_WORK_PROFILE_APP_UID)})
+                .setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+        final TestOnCompleteListener listener = new TestOnCompleteListener();
+        mCm.setProfileNetworkPreferences(testHandle,
+                List.of(prefBuilder.build()),
+                r -> r.run(), listener);
+        listener.expectOnComplete();
+        if (mDeps.isAtLeastU()) {
+            inOrder.verify(mMockNetd).setNetworkAllowlist(new NativeUidRangeConfig[]{});
+        } else {
+            inOrder.verify(mMockNetd, never()).setNetworkAllowlist(any());
+        }
+
+        // Start with 1 default network, which should be restricted since the blocking
+        // preference is already set.
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+
+        // Verify cellular network applies to the allow list.
+        // Cell: 0~99999, 200000~TEST_APP_UID-1, TEST_APP_UID+1~299999
+        // Enterprise: 0~99999, 200000~299999
+        final ArraySet<UidRange> excludeAppRanges = new ArraySet<>();
+        final UidRange profileUidRange =
+                UidRange.createForUser(UserHandle.of(TEST_WORK_PROFILE_USER_ID));
+        excludeAppRanges.add(PRIMARY_UIDRANGE);
+        excludeAppRanges.addAll(UidRangeUtils.removeRangeSetFromUidRange(
+                profileUidRange,
+                new ArraySet(new UidRange[]{
+                        (new UidRange(TEST_WORK_PROFILE_APP_UID, TEST_WORK_PROFILE_APP_UID))})
+        ));
+        final UidRangeParcel[] excludeAppRangesParcel = toUidRangeStableParcels(excludeAppRanges);
+        final NativeUidRangeConfig cellExcludeAppConfig = new NativeUidRangeConfig(
+                mCellAgent.getNetwork().netId,
+                excludeAppRangesParcel,
+                0 /* subPriority */);
+        if (mDeps.isAtLeastU()) {
+            inOrder.verify(mMockNetd).setNetworkAllowlist(
+                    new NativeUidRangeConfig[]{cellExcludeAppConfig});
+        } else {
+            inOrder.verify(mMockNetd, never()).setNetworkAllowlist(any());
+        }
+
+        // Verify enterprise network is not blocked for test app.
+        final TestNetworkAgentWrapper enterpriseAgent = makeEnterpriseNetworkAgent(
+                NET_ENTERPRISE_ID_1);
+        enterpriseAgent.connect(true);
+        ArraySet<UidRange> allowedAllUidRanges = new ArraySet<>();
+        allowedAllUidRanges.add(PRIMARY_UIDRANGE);
+        allowedAllUidRanges.add(profileUidRange);
+        final UidRangeParcel[] allowAllUidRangesParcel = toUidRangeStableParcels(
+                allowedAllUidRanges);
+        final NativeUidRangeConfig enterpriseAllAllowedConfig = new NativeUidRangeConfig(
+                enterpriseAgent.getNetwork().netId,
+                allowAllUidRangesParcel,
+                0 /* subPriority */);
+        // Network agents are stored in an ArraySet which does not guarantee the order and
+        // making the order of the list undeterministic. Thus, verify this in order insensitive way.
+        final ArgumentCaptor<NativeUidRangeConfig[]> configsCaptor = ArgumentCaptor.forClass(
+                NativeUidRangeConfig[].class);
+        if (mDeps.isAtLeastU()) {
+            inOrder.verify(mMockNetd).setNetworkAllowlist(configsCaptor.capture());
+            assertContainsAll(List.of(configsCaptor.getValue()),
+                    List.of(enterpriseAllAllowedConfig, cellExcludeAppConfig));
+        } else {
+            inOrder.verify(mMockNetd, never()).setNetworkAllowlist(any());
+        }
+
+        // Verify issuing with cellular set only when enterprise network disconnects.
+        enterpriseAgent.disconnect();
+        waitForIdle();
+        if (mDeps.isAtLeastU()) {
+            inOrder.verify(mMockNetd).setNetworkAllowlist(
+                    new NativeUidRangeConfig[]{cellExcludeAppConfig});
+        } else {
+            inOrder.verify(mMockNetd, never()).setNetworkAllowlist(any());
+        }
+
+        mCellAgent.disconnect();
+        waitForIdle();
+        if (mDeps.isAtLeastU()) {
+            inOrder.verify(mMockNetd).setNetworkAllowlist(new NativeUidRangeConfig[]{});
+        } else {
+            inOrder.verify(mMockNetd, never()).setNetworkAllowlist(any());
+        }
+    }
+
     /**
      * Make sure wrong preferences for per-profile default networking are rejected.
      */
@@ -15762,7 +16530,7 @@
         ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
                 new ProfileNetworkPreference.Builder();
         profileNetworkPreferenceBuilder.setPreference(
-                PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK + 1);
+                PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING + 1);
         profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
         assertThrows("Should not be able to set an illegal preference",
                 IllegalArgumentException.class,
@@ -15801,7 +16569,7 @@
         profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
         profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
         final TestOnCompleteListener listener = new TestOnCompleteListener();
-        if (SdkLevel.isAtLeastT()) {
+        if (mDeps.isAtLeastT()) {
             mCm.setProfileNetworkPreferences(testHandle,
                     List.of(profileNetworkPreferenceBuilder.build()),
                     r -> r.run(), listener);
@@ -15909,7 +16677,7 @@
                 agent.getNetwork().getNetId(),
                 intToUidRangeStableParcels(uids),
                 preferenceOrder);
-        if (SdkLevel.isAtLeastT()) {
+        if (mDeps.isAtLeastT()) {
             inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids200Parcel);
         }
 
@@ -15917,8 +16685,8 @@
         uids.add(400);
         nc.setAllowedUids(uids);
         agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
-        if (SdkLevel.isAtLeastT()) {
-            cb.expectCapabilitiesThat(agent, caps -> caps.getAllowedUids().equals(uids));
+        if (mDeps.isAtLeastT()) {
+            cb.expectCaps(agent, c -> c.getAllowedUids().equals(uids));
         } else {
             cb.assertNoCallback();
         }
@@ -15928,14 +16696,14 @@
                 agent.getNetwork().getNetId(),
                 intToUidRangeStableParcels(uids),
                 preferenceOrder);
-        if (SdkLevel.isAtLeastT()) {
+        if (mDeps.isAtLeastT()) {
             inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids300400Parcel);
         }
 
         nc.setAllowedUids(uids);
         agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
-        if (SdkLevel.isAtLeastT()) {
-            cb.expectCapabilitiesThat(agent, caps -> caps.getAllowedUids().equals(uids));
+        if (mDeps.isAtLeastT()) {
+            cb.expectCaps(agent, c -> c.getAllowedUids().equals(uids));
             inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids200Parcel);
         } else {
             cb.assertNoCallback();
@@ -15945,8 +16713,8 @@
         uids.add(600);
         nc.setAllowedUids(uids);
         agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
-        if (SdkLevel.isAtLeastT()) {
-            cb.expectCapabilitiesThat(agent, caps -> caps.getAllowedUids().equals(uids));
+        if (mDeps.isAtLeastT()) {
+            cb.expectCaps(agent, c -> c.getAllowedUids().equals(uids));
         } else {
             cb.assertNoCallback();
         }
@@ -15954,7 +16722,7 @@
                 agent.getNetwork().getNetId(),
                 intToUidRangeStableParcels(uids),
                 preferenceOrder);
-        if (SdkLevel.isAtLeastT()) {
+        if (mDeps.isAtLeastT()) {
             inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids600Parcel);
             inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids300400Parcel);
         }
@@ -15962,8 +16730,8 @@
         uids.clear();
         nc.setAllowedUids(uids);
         agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
-        if (SdkLevel.isAtLeastT()) {
-            cb.expectCapabilitiesThat(agent, caps -> caps.getAllowedUids().isEmpty());
+        if (mDeps.isAtLeastT()) {
+            cb.expectCaps(agent, c -> c.getAllowedUids().isEmpty());
             inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids600Parcel);
         } else {
             cb.assertNoCallback();
@@ -15997,7 +16765,7 @@
                 .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
                 .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
 
-        mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET,
+        mEthernetAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET,
                 new LinkProperties(), ncb.build());
 
         final ArraySet<Integer> serviceUidSet = new ArraySet<>();
@@ -16009,22 +16777,21 @@
                 .addTransportType(TRANSPORT_ETHERNET)
                 .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
                 .build(), cb);
-        mEthernetNetworkAgent.connect(true);
-        cb.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
+        mEthernetAgent.connect(true);
+        cb.expectAvailableThenValidatedCallbacks(mEthernetAgent);
 
         // Cell gets to set the service UID as access UID
         ncb.setAllowedUids(serviceUidSet);
-        mEthernetNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
-        if (SdkLevel.isAtLeastT() && hasAutomotiveFeature) {
-            cb.expectCapabilitiesThat(mEthernetNetworkAgent,
-                    caps -> caps.getAllowedUids().equals(serviceUidSet));
+        mEthernetAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+        if (mDeps.isAtLeastT() && hasAutomotiveFeature) {
+            cb.expectCaps(mEthernetAgent, c -> c.getAllowedUids().equals(serviceUidSet));
         } else {
             // S and no automotive feature must ignore access UIDs.
             cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
         }
 
-        mEthernetNetworkAgent.disconnect();
-        cb.expect(CallbackEntry.LOST, mEthernetNetworkAgent);
+        mEthernetAgent.disconnect();
+        cb.expect(LOST, mEthernetAgent);
         mCm.unregisterNetworkCallback(cb);
     }
 
@@ -16047,7 +16814,7 @@
                 .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
                 .setNetworkSpecifier(new TelephonyNetworkSpecifier(1 /* subid */));
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
                 new LinkProperties(), ncb.build());
 
         final ArraySet<Integer> serviceUidSet = new ArraySet<>();
@@ -16065,13 +16832,12 @@
                 .addTransportType(TRANSPORT_CELLULAR)
                 .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
                 .build(), cb);
-        mCellNetworkAgent.connect(true);
-        cb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent.connect(true);
+        cb.expectAvailableThenValidatedCallbacks(mCellAgent);
         ncb.setAllowedUids(serviceUidSet);
-        mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
-        if (SdkLevel.isAtLeastT()) {
-            cb.expectCapabilitiesThat(mCellNetworkAgent,
-                    caps -> caps.getAllowedUids().equals(serviceUidSet));
+        mCellAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+        if (mDeps.isAtLeastT()) {
+            cb.expectCaps(mCellAgent, c -> c.getAllowedUids().equals(serviceUidSet));
         } else {
             // S must ignore access UIDs.
             cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
@@ -16079,10 +16845,9 @@
 
         // ...but not to some other UID. Rejection sets UIDs to the empty set
         ncb.setAllowedUids(nonServiceUidSet);
-        mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
-        if (SdkLevel.isAtLeastT()) {
-            cb.expectCapabilitiesThat(mCellNetworkAgent,
-                    caps -> caps.getAllowedUids().isEmpty());
+        mCellAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+        if (mDeps.isAtLeastT()) {
+            cb.expectCaps(mCellAgent, c -> c.getAllowedUids().isEmpty());
         } else {
             // S must ignore access UIDs.
             cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
@@ -16090,11 +16855,11 @@
 
         // ...and also not to multiple UIDs even including the service UID
         ncb.setAllowedUids(serviceUidSetPlus);
-        mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+        mCellAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
         cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
 
-        mCellNetworkAgent.disconnect();
-        cb.expect(CallbackEntry.LOST, mCellNetworkAgent);
+        mCellAgent.disconnect();
+        cb.expect(LOST, mCellAgent);
         mCm.unregisterNetworkCallback(cb);
 
         // Must be unset before touching the transports, because remove and add transport types
@@ -16108,12 +16873,11 @@
                 .addTransportType(TRANSPORT_WIFI)
                 .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
                 .build(), cb);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI,
-                new LinkProperties(), ncb.build());
-        mWiFiNetworkAgent.connect(true);
-        cb.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, new LinkProperties(), ncb.build());
+        mWiFiAgent.connect(true);
+        cb.expectAvailableThenValidatedCallbacks(mWiFiAgent);
         ncb.setAllowedUids(serviceUidSet);
-        mWiFiNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+        mWiFiAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
         cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
         mCm.unregisterNetworkCallback(cb);
     }
@@ -16184,7 +16948,7 @@
             final NetworkCallback[] callbacks = new NetworkCallback[remainingCount];
             doAsUid(otherAppUid, () -> {
                 for (int i = 0; i < remainingCount; ++i) {
-                    callbacks[i] = new TestableNetworkCallback();
+                    callbacks[i] = new TestNetworkCallback();
                     mCm.registerDefaultNetworkCallback(callbacks[i]);
                 }
             });
@@ -16300,12 +17064,12 @@
     public void testMobileDataPreferredUidsChanged() throws Exception {
         final InOrder inorder = inOrder(mMockNetd);
         registerDefaultNetworkCallbacks();
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
 
-        final int cellNetId = mCellNetworkAgent.getNetwork().netId;
+        final int cellNetId = mCellAgent.getNetwork().netId;
         inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(
                 cellNetId, INetd.PERMISSION_NONE));
 
@@ -16358,13 +17122,13 @@
         cellNetworkCallback.assertNoCallback();
 
         registerDefaultNetworkCallbacks();
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
-        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
-        mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
+        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiAgent);
+        mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiAgent);
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
 
-        final int wifiNetId = mWiFiNetworkAgent.getNetwork().netId;
+        final int wifiNetId = mWiFiAgent.getNetwork().netId;
         inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(
                 wifiNetId, INetd.PERMISSION_NONE));
 
@@ -16385,14 +17149,14 @@
 
         // Cellular network connected. mTestPackageDefaultNetworkCallback should receive
         // callback with cellular network and net id and uid ranges should be updated to netd.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
         mDefaultNetworkCallback.assertNoCallback();
-        mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
+        mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
 
-        final int cellNetId = mCellNetworkAgent.getNetwork().netId;
+        final int cellNetId = mCellAgent.getNetwork().netId;
         final NativeUidRangeConfig cellConfig = new NativeUidRangeConfig(cellNetId, uidRanges,
                 PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED);
         inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(
@@ -16402,26 +17166,26 @@
 
         // Cellular network disconnected. mTestPackageDefaultNetworkCallback should receive
         // callback with wifi network from fallback request.
-        mCellNetworkAgent.disconnect();
+        mCellAgent.disconnect();
         mDefaultNetworkCallback.assertNoCallback();
-        cellNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
-        mTestPackageDefaultNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
-        mTestPackageDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
+        cellNetworkCallback.expect(LOST, mCellAgent);
+        mTestPackageDefaultNetworkCallback.expect(LOST, mCellAgent);
+        mTestPackageDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiAgent);
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
         inorder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(wifiConfig);
         inorder.verify(mMockNetd, never()).networkRemoveUidRangesParcel(any());
         inorder.verify(mMockNetd).networkDestroy(cellNetId);
 
         // Cellular network comes back. mTestPackageDefaultNetworkCallback should receive
         // callback with cellular network.
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
         mDefaultNetworkCallback.assertNoCallback();
-        mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
+        mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
 
-        final int cellNetId2 = mCellNetworkAgent.getNetwork().netId;
+        final int cellNetId2 = mCellAgent.getNetwork().netId;
         final NativeUidRangeConfig cellConfig2 = new NativeUidRangeConfig(cellNetId2, uidRanges,
                 PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED);
         inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(
@@ -16431,11 +17195,11 @@
 
         // Wifi network disconnected. mTestPackageDefaultNetworkCallback should not receive
         // any callback.
-        mWiFiNetworkAgent.disconnect();
-        mDefaultNetworkCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        mWiFiAgent.disconnect();
+        mDefaultNetworkCallback.expect(LOST, mWiFiAgent);
+        mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellAgent);
         mTestPackageDefaultNetworkCallback.assertNoCallback();
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
         waitForIdle();
         inorder.verify(mMockNetd, never()).networkAddUidRangesParcel(any());
         inorder.verify(mMockNetd, never()).networkRemoveUidRangesParcel(any());
@@ -16467,15 +17231,15 @@
             cellFactory.expectRequestAdds(2);
             cellFactory.assertRequestCountEquals(2);
 
-            mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-            mWiFiNetworkAgent.connect(true);
+            mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+            mWiFiAgent.connect(true);
 
             // The cellFactory however is outscored, and should lose default internet request.
             // But it should still see mobile data preferred request.
             cellFactory.expectRequestRemove();
             cellFactory.assertRequestCountEquals(1);
 
-            mWiFiNetworkAgent.disconnect();
+            mWiFiAgent.disconnect();
             // The network satisfying the default internet request has disconnected, so the
             // cellFactory sees the default internet requests again.
             cellFactory.expectRequestAdd();
@@ -16483,6 +17247,7 @@
         } finally {
             cellFactory.terminate();
             handlerThread.quitSafely();
+            handlerThread.join();
         }
     }
 
@@ -16517,7 +17282,7 @@
         final UserHandle testHandle = setupEnterpriseNetwork();
 
         setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
-        final int cellNetId = mCellNetworkAgent.getNetwork().netId;
+        final int cellNetId = mCellAgent.getNetwork().netId;
         inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(
                 cellNetId, INetd.PERMISSION_NONE));
 
@@ -16590,28 +17355,28 @@
 
         // Register callbacks and have wifi network as default network.
         registerDefaultNetworkCallbacks();
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
-        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
-        mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
-        mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.getNetwork(),
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(true);
+        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiAgent);
+        mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiAgent);
+        mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiAgent);
+        assertEquals(mWiFiAgent.getNetwork(),
                 mCm.getActiveNetworkForUid(TEST_WORK_PROFILE_APP_UID));
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
 
         // Set MOBILE_DATA_PREFERRED_UIDS setting with TEST_WORK_PROFILE_APP_UID and
         // TEST_PACKAGE_UID. Both mProfileDefaultNetworkCallback and
         // mTestPackageDefaultNetworkCallback should receive callback with cell network.
         setAndUpdateMobileDataPreferredUids(Set.of(TEST_WORK_PROFILE_APP_UID, TEST_PACKAGE_UID));
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
+        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
         mDefaultNetworkCallback.assertNoCallback();
-        mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        assertEquals(mCellNetworkAgent.getNetwork(),
+        mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellAgent);
+        assertEquals(mCellAgent.getNetwork(),
                 mCm.getActiveNetworkForUid(TEST_WORK_PROFILE_APP_UID));
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
 
         // Set user profile network preference with test profile. mProfileDefaultNetworkCallback
         // should receive callback with higher priority network preference (enterprise network).
@@ -16626,7 +17391,7 @@
         assertNoCallbacks(mDefaultNetworkCallback, mTestPackageDefaultNetworkCallback);
         mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(workAgent);
         assertEquals(workAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_WORK_PROFILE_APP_UID));
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
 
         // Set oem network preference with TEST_PACKAGE_UID. mTestPackageDefaultNetworkCallback
         // should receive callback with higher priority network preference (current default network)
@@ -16637,8 +17402,8 @@
         final UidRangeParcel[] uidRanges1 = toUidRangeStableParcels(uidRangesForUids(uids1));
         setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges1, TEST_PACKAGE_NAME);
         assertNoCallbacks(mDefaultNetworkCallback, mProfileDefaultNetworkCallback);
-        mTestPackageDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
+        mTestPackageDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiAgent);
+        assertEquals(mWiFiAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
         assertEquals(workAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_WORK_PROFILE_APP_UID));
 
         // Set oem network preference with TEST_WORK_PROFILE_APP_UID. Both
@@ -16650,11 +17415,11 @@
         setupSetOemNetworkPreferenceForPreferenceTest(
                 networkPref, uidRanges2, "com.android.test", testHandle);
         mDefaultNetworkCallback.assertNoCallback();
-        mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
-        mTestPackageDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
-        assertEquals(mWiFiNetworkAgent.getNetwork(),
+        mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiAgent);
+        mTestPackageDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellAgent);
+        assertEquals(mWiFiAgent.getNetwork(),
                 mCm.getActiveNetworkForUid(TEST_WORK_PROFILE_APP_UID));
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
 
         // Remove oem network preference, mProfileDefaultNetworkCallback should receive callback
         // with current highest priority network preference (enterprise network) and the others
@@ -16666,23 +17431,23 @@
         assertNoCallbacks(mDefaultNetworkCallback, mTestPackageDefaultNetworkCallback);
         mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(workAgent);
         assertEquals(workAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_WORK_PROFILE_APP_UID));
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
 
         // Remove user profile network preference.
         mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_DEFAULT,
                 r -> r.run(), listener);
         listener.expectOnComplete();
         assertNoCallbacks(mDefaultNetworkCallback, mTestPackageDefaultNetworkCallback);
-        mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
-        assertEquals(mCellNetworkAgent.getNetwork(),
+        mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellAgent);
+        assertEquals(mCellAgent.getNetwork(),
                 mCm.getActiveNetworkForUid(TEST_WORK_PROFILE_APP_UID));
-        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
+        assertEquals(mCellAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
 
         // Disconnect wifi
-        mWiFiNetworkAgent.disconnect();
+        mWiFiAgent.disconnect();
         assertNoCallbacks(mProfileDefaultNetworkCallback, mTestPackageDefaultNetworkCallback);
-        mDefaultNetworkCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
-        mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        mDefaultNetworkCallback.expect(LOST, mWiFiAgent);
+        mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellAgent);
     }
 
     @Test
@@ -16696,13 +17461,13 @@
     public void testUpdateRateLimit_EnableDisable() throws Exception {
         final LinkProperties wifiLp = new LinkProperties();
         wifiLp.setInterfaceName(WIFI_IFNAME);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiAgent.connect(true);
 
         final LinkProperties cellLp = new LinkProperties();
         cellLp.setInterfaceName(MOBILE_IFNAME);
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
-        mCellNetworkAgent.connect(false);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mCellAgent.connect(false);
 
         waitForIdle();
 
@@ -16735,8 +17500,8 @@
     public void testUpdateRateLimit_WhenNewNetworkIsAdded() throws Exception {
         final LinkProperties wifiLp = new LinkProperties();
         wifiLp.setInterfaceName(WIFI_IFNAME);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiAgent.connect(true);
 
         waitForIdle();
 
@@ -16751,8 +17516,8 @@
 
         final LinkProperties cellLp = new LinkProperties();
         cellLp.setInterfaceName(MOBILE_IFNAME);
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
-        mCellNetworkAgent.connect(false);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mCellAgent.connect(false);
         assertNotNull(readHead.poll(TIMEOUT_MS, it -> it.first == cellLp.getInterfaceName()
                 && it.second == rateLimitInBytesPerSec));
     }
@@ -16762,8 +17527,8 @@
         final LinkProperties wifiLp = new LinkProperties();
         wifiLp.setInterfaceName(WIFI_IFNAME);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
-        mWiFiNetworkAgent.connectWithoutInternet();
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiAgent.connectWithoutInternet();
 
         waitForIdle();
 
@@ -16787,8 +17552,8 @@
         // - ensure network interface is not rate limited
         final LinkProperties wifiLp = new LinkProperties();
         wifiLp.setInterfaceName(WIFI_IFNAME);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiAgent.connect(true);
         waitForIdle();
 
         final ArrayTrackRecord<Pair<String, Long>>.ReadHead readHeadWifi =
@@ -16800,14 +17565,14 @@
                 it -> it.first == wifiLp.getInterfaceName()
                         && it.second == rateLimitInBytesPerSec));
 
-        mWiFiNetworkAgent.disconnect();
+        mWiFiAgent.disconnect();
         assertNotNull(readHeadWifi.poll(TIMEOUT_MS,
                 it -> it.first == wifiLp.getInterfaceName() && it.second == -1));
 
         setIngressRateLimit(-1);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiAgent.connect(true);
         assertNull(readHeadWifi.poll(TIMEOUT_MS, it -> it.first == wifiLp.getInterfaceName()));
     }
 
@@ -16815,8 +17580,8 @@
     public void testUpdateRateLimit_UpdateExistingRateLimit() throws Exception {
         final LinkProperties wifiLp = new LinkProperties();
         wifiLp.setInterfaceName(WIFI_IFNAME);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiAgent.connect(true);
         waitForIdle();
 
         final ArrayTrackRecord<Pair<String, Long>>.ReadHead readHeadWifi =
@@ -16845,8 +17610,8 @@
     public void testUpdateRateLimit_DoesNothingBeforeT() throws Exception {
         final LinkProperties wifiLp = new LinkProperties();
         wifiLp.setInterfaceName(WIFI_IFNAME);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiAgent.connect(true);
         waitForIdle();
 
         final ArrayTrackRecord<Pair<String, Long>>.ReadHead readHead =
@@ -16885,8 +17650,8 @@
 
         final String bssid1 = "AA:AA:AA:AA:AA:AA";
         final String bssid2 = "BB:BB:BB:BB:BB:BB";
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellAgent.connect(true);
         NetworkCapabilities wifiNc1 = new NetworkCapabilities()
                 .addCapability(NET_CAPABILITY_INTERNET)
                 .addCapability(NET_CAPABILITY_NOT_VPN)
@@ -16899,105 +17664,100 @@
                 .setTransportInfo(new WifiInfo.Builder().setBssid(bssid2).build());
         final LinkProperties wifiLp = new LinkProperties();
         wifiLp.setInterfaceName(WIFI_IFNAME);
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp, wifiNc1);
-        mWiFiNetworkAgent.connect(true);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp, wifiNc1);
+        mWiFiAgent.connect(true);
 
         // The default network will be switching to Wi-Fi Network.
         final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
         final NetworkRequest wifiRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_WIFI).build();
         mCm.requestNetwork(wifiRequest, wifiNetworkCallback);
-        wifiNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
+        wifiNetworkCallback.expectAvailableCallbacksValidated(mWiFiAgent);
         registerDefaultNetworkCallbacks();
-        mDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
+        mDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiAgent);
 
         // There is a bug in the current code where ignoring validation after roam will not
         // correctly change the default network if the result if the validation is partial or
         // captive portal. TODO : fix the bug and reinstate this code.
         if (false) {
             // Wi-Fi roaming from wifiNc1 to wifiNc2 but the network is now behind a captive portal.
-            mWiFiNetworkAgent.setNetworkCapabilities(wifiNc2, true /* sendToConnectivityService */);
+            mWiFiAgent.setNetworkCapabilities(wifiNc2, true /* sendToConnectivityService */);
             // The only thing changed in this CAPS is the BSSID, which can't be tested for in this
             // test because it's redacted.
-            wifiNetworkCallback.expect(CallbackEntry.NETWORK_CAPS_UPDATED,
-                    mWiFiNetworkAgent);
-            mDefaultNetworkCallback.expect(CallbackEntry.NETWORK_CAPS_UPDATED,
-                    mWiFiNetworkAgent);
-            mWiFiNetworkAgent.setNetworkPortal(TEST_REDIRECT_URL, false /* isStrictMode */);
-            mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
+            wifiNetworkCallback.expectCaps(mWiFiAgent);
+            mDefaultNetworkCallback.expectCaps(mWiFiAgent);
+            mWiFiAgent.setNetworkPortal(TEST_REDIRECT_URL, false /* privateDnsProbeSent */);
+            mCm.reportNetworkConnectivity(mWiFiAgent.getNetwork(), false);
             // Wi-Fi is now detected to have a portal : cell should become the default network.
-            mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
-            wifiNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED,
-                    mWiFiNetworkAgent);
-            wifiNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_CAPTIVE_PORTAL,
-                    mWiFiNetworkAgent);
+            mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellAgent);
+            wifiNetworkCallback.expectCaps(mWiFiAgent,
+                    c -> !c.hasCapability(NET_CAPABILITY_VALIDATED));
+            wifiNetworkCallback.expectCaps(mWiFiAgent,
+                    c -> c.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL));
 
             // Wi-Fi becomes valid again. The default network goes back to Wi-Fi.
-            mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
-            mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
-            mDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
-            wifiNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_CAPTIVE_PORTAL,
-                    mWiFiNetworkAgent);
+            mWiFiAgent.setNetworkValid(false /* privateDnsProbeSent */);
+            mCm.reportNetworkConnectivity(mWiFiAgent.getNetwork(), true);
+            mDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiAgent);
+            wifiNetworkCallback.expectCaps(mWiFiAgent,
+                    c -> !c.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL));
 
             // Wi-Fi roaming from wifiNc2 to wifiNc1, and the network now has partial connectivity.
-            mWiFiNetworkAgent.setNetworkCapabilities(wifiNc1, true);
-            wifiNetworkCallback.expect(CallbackEntry.NETWORK_CAPS_UPDATED,
-                    mWiFiNetworkAgent);
-            mDefaultNetworkCallback.expect(CallbackEntry.NETWORK_CAPS_UPDATED,
-                    mWiFiNetworkAgent);
-            mWiFiNetworkAgent.setNetworkPartial();
-            mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
+            mWiFiAgent.setNetworkCapabilities(wifiNc1, true);
+            wifiNetworkCallback.expectCaps(mWiFiAgent);
+            mDefaultNetworkCallback.expectCaps(mWiFiAgent);
+            mWiFiAgent.setNetworkPartial();
+            mCm.reportNetworkConnectivity(mWiFiAgent.getNetwork(), false);
             // Wi-Fi now only offers partial connectivity, so in the absence of accepting partial
             // connectivity explicitly for this network, it loses default status to cell.
-            mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
-            wifiNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
-                    mWiFiNetworkAgent);
+            mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellAgent);
+            wifiNetworkCallback.expectCaps(mWiFiAgent,
+                    c -> c.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
 
             // Wi-Fi becomes valid again. The default network goes back to Wi-Fi.
-            mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
-            mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
-            mDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
-            wifiNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
-                    mWiFiNetworkAgent);
+            mWiFiAgent.setNetworkValid(false /* privateDnsProbeSent */);
+            mCm.reportNetworkConnectivity(mWiFiAgent.getNetwork(), true);
+            mDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiAgent);
+            wifiNetworkCallback.expectCaps(mWiFiAgent,
+                    c -> !c.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
         }
         mCm.unregisterNetworkCallback(wifiNetworkCallback);
 
         // Wi-Fi roams from wifiNc1 to wifiNc2, and now becomes really invalid. If validation
         // failures after roam are not ignored, this will cause cell to become the default network.
         // If they are ignored, this will not cause a switch until later.
-        mWiFiNetworkAgent.setNetworkCapabilities(wifiNc2, true);
-        mDefaultNetworkCallback.expect(CallbackEntry.NETWORK_CAPS_UPDATED,
-                mWiFiNetworkAgent);
-        mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */);
-        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
+        mWiFiAgent.setNetworkCapabilities(wifiNc2, true);
+        mDefaultNetworkCallback.expectCaps(mWiFiAgent);
+        mWiFiAgent.setNetworkInvalid(false /* invalidBecauseOfPrivateDns */);
+        mCm.reportNetworkConnectivity(mWiFiAgent.getNetwork(), false);
 
         if (enabled) {
             // Network validation failed, but the result will be ignored.
-            assertTrue(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
+            assertTrue(mCm.getNetworkCapabilities(mWiFiAgent.getNetwork()).hasCapability(
                     NET_CAPABILITY_VALIDATED));
-            mWiFiNetworkAgent.setNetworkValid(false);
+            mWiFiAgent.setNetworkValid(false);
 
             // Behavior of after config_validationFailureAfterRoamIgnoreTimeMillis
             ConditionVariable waitForValidationBlock = new ConditionVariable();
             doReturn(50).when(mResources)
                     .getInteger(R.integer.config_validationFailureAfterRoamIgnoreTimeMillis);
             // Wi-Fi roaming from wifiNc2 to wifiNc1.
-            mWiFiNetworkAgent.setNetworkCapabilities(wifiNc1, true);
-            mWiFiNetworkAgent.setNetworkInvalid(false);
+            mWiFiAgent.setNetworkCapabilities(wifiNc1, true);
+            mWiFiAgent.setNetworkInvalid(false);
             waitForValidationBlock.block(150);
-            mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
-            mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+            mCm.reportNetworkConnectivity(mWiFiAgent.getNetwork(), false);
+            mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellAgent);
         } else {
-            mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+            mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellAgent);
         }
 
         // Wi-Fi is still connected and would become the default network if cell were to
         // disconnect. This assertion ensures that the switch to cellular was not caused by
         // Wi-Fi disconnecting (e.g., because the capability change to wifiNc2 caused it
         // to stop satisfying the default request).
-        mCellNetworkAgent.disconnect();
-        mDefaultNetworkCallback.expect(CallbackEntry.LOST, mCellNetworkAgent);
-        mDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mCellAgent.disconnect();
+        mDefaultNetworkCallback.expect(LOST, mCellAgent);
+        mDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiAgent);
 
     }
 
@@ -17008,14 +17768,14 @@
 
     @Test
     public void testIgnoreValidationAfterRoamEnabled() throws Exception {
-        final boolean enabled = !SdkLevel.isAtLeastT();
+        final boolean enabled = !mDeps.isAtLeastT();
         doTestIgnoreValidationAfterRoam(5_000, enabled);
     }
 
     @Test
     public void testShouldIgnoreValidationFailureAfterRoam() {
         // Always disabled on T+.
-        assumeFalse(SdkLevel.isAtLeastT());
+        assumeFalse(mDeps.isAtLeastT());
 
         NetworkAgentInfo nai = fakeWifiNai(new NetworkCapabilities());
 
@@ -17073,18 +17833,93 @@
         });
     }
 
+    private void verifyMtuSetOnWifiInterface(int mtu) throws Exception {
+        verify(mMockNetd, times(1)).interfaceSetMtu(WIFI_IFNAME, mtu);
+    }
+
+    private void verifyMtuNeverSetOnWifiInterface() throws Exception {
+        verify(mMockNetd, never()).interfaceSetMtu(eq(WIFI_IFNAME), anyInt());
+    }
+
+    private void verifyMtuSetOnWifiInterfaceOnlyUpToT(int mtu) throws Exception {
+        if (!mService.shouldCreateNetworksImmediately()) {
+            verify(mMockNetd, times(1)).interfaceSetMtu(WIFI_IFNAME, mtu);
+        } else {
+            verify(mMockNetd, never()).interfaceSetMtu(eq(WIFI_IFNAME), anyInt());
+        }
+    }
+
+    private void verifyMtuSetOnWifiInterfaceOnlyStartingFromU(int mtu) throws Exception {
+        if (mService.shouldCreateNetworksImmediately()) {
+            verify(mMockNetd, times(1)).interfaceSetMtu(WIFI_IFNAME, mtu);
+        } else {
+            verify(mMockNetd, never()).interfaceSetMtu(eq(WIFI_IFNAME), anyInt());
+        }
+    }
+
     @Test
-    public void testSendLinkPropertiesSetInterfaceMtu() throws Exception {
+    public void testSendLinkPropertiesSetInterfaceMtuBeforeConnect() throws Exception {
+        final int mtu = 1281;
+        LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(WIFI_IFNAME);
+        lp.setMtu(mtu);
+
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.sendLinkProperties(lp);
+        waitForIdle();
+        verifyMtuSetOnWifiInterface(mtu);
+        reset(mMockNetd);
+
+        mWiFiAgent.connect(false /* validated */);
+        // Before U, the MTU is always (re-)applied when the network connects.
+        verifyMtuSetOnWifiInterfaceOnlyUpToT(mtu);
+    }
+
+    @Test
+    public void testSendLinkPropertiesUpdateInterfaceMtuBeforeConnect() throws Exception {
         final int mtu = 1327;
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName(WIFI_IFNAME);
         lp.setMtu(mtu);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.sendLinkProperties(lp);
-
+        // Registering an agent with an MTU only sets the MTU on U+.
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
         waitForIdle();
-        verify(mMockNetd).interfaceSetMtu(eq(WIFI_IFNAME), eq(mtu));
+        verifyMtuSetOnWifiInterfaceOnlyStartingFromU(mtu);
+        reset(mMockNetd);
+
+        // Future updates with the same MTU don't set the MTU even on T when it's not set initially.
+        mWiFiAgent.sendLinkProperties(lp);
+        waitForIdle();
+        verifyMtuNeverSetOnWifiInterface();
+
+        // Updating with a different MTU does work.
+        lp.setMtu(mtu + 1);
+        mWiFiAgent.sendLinkProperties(lp);
+        waitForIdle();
+        verifyMtuSetOnWifiInterface(mtu + 1);
+        reset(mMockNetd);
+
+        mWiFiAgent.connect(false /* validated */);
+        // Before U, the MTU is always (re-)applied when the network connects.
+        verifyMtuSetOnWifiInterfaceOnlyUpToT(mtu + 1);
+    }
+
+    @Test
+    public void testSendLinkPropertiesUpdateInterfaceMtuAfterConnect() throws Exception {
+        final int mtu = 1327;
+        LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(WIFI_IFNAME);
+        lp.setMtu(mtu);
+
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiAgent.connect(false /* validated */);
+        verifyMtuNeverSetOnWifiInterface();
+
+        mWiFiAgent.sendLinkProperties(lp);
+        waitForIdle();
+        // The MTU is always (re-)applied when the network connects.
+        verifyMtuSetOnWifiInterface(mtu);
     }
 
     @Test
@@ -17094,15 +17929,16 @@
         lp.setInterfaceName(WIFI_IFNAME);
         lp.setMtu(mtu);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
+        mWiFiAgent.connect(false /* validated */);
+        verifyMtuSetOnWifiInterface(mtu);
+        reset(mMockNetd);
 
         LinkProperties lp2 = new LinkProperties(lp);
         lp2.setMtu(mtu2);
-
-        mWiFiNetworkAgent.sendLinkProperties(lp2);
-
+        mWiFiAgent.sendLinkProperties(lp2);
         waitForIdle();
-        verify(mMockNetd).interfaceSetMtu(eq(WIFI_IFNAME), eq(mtu2));
+        verifyMtuSetOnWifiInterface(mtu2);
     }
 
     @Test
@@ -17112,11 +17948,14 @@
         lp.setInterfaceName(WIFI_IFNAME);
         lp.setMtu(mtu);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
-        mWiFiNetworkAgent.sendLinkProperties(new LinkProperties(lp));
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
+        mWiFiAgent.connect(false /* validated */);
+        verifyMtuSetOnWifiInterface(mtu);
+        reset(mMockNetd);
 
+        mWiFiAgent.sendLinkProperties(new LinkProperties(lp));
         waitForIdle();
-        verify(mMockNetd, never()).interfaceSetMtu(eq(WIFI_IFNAME), anyInt());
+        verifyMtuNeverSetOnWifiInterface();
     }
 
     @Test
@@ -17126,16 +17965,16 @@
         lp.setInterfaceName(WIFI_IFNAME);
         lp.setMtu(mtu);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
+        mWiFiAgent.connect(false /* validated */);
+        verifyMtuSetOnWifiInterface(mtu);
+        reset(mMockNetd);
 
-        LinkProperties lp2 = new LinkProperties();
-        assertNull(lp2.getInterfaceName());
-        lp2.setMtu(mtu);
-
-        mWiFiNetworkAgent.sendLinkProperties(new LinkProperties(lp2));
-
+        LinkProperties lp2 = new LinkProperties(lp);
+        lp2.setInterfaceName(null);
+        mWiFiAgent.sendLinkProperties(new LinkProperties(lp2));
         waitForIdle();
-        verify(mMockNetd, never()).interfaceSetMtu(any(), anyInt());
+        verifyMtuNeverSetOnWifiInterface();
     }
 
     @Test
@@ -17145,17 +17984,132 @@
         lp.setInterfaceName(WIFI_IFNAME);
         lp.setMtu(mtu);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp);
+        mWiFiAgent.connect(false /* validated */);
+        verifyMtuSetOnWifiInterface(mtu);
+        reset(mMockNetd);
 
         final String ifaceName2 = WIFI_IFNAME + "_2";
-        LinkProperties lp2 = new LinkProperties();
+        LinkProperties lp2 = new LinkProperties(lp);
         lp2.setInterfaceName(ifaceName2);
-        lp2.setMtu(mtu);
 
-        mWiFiNetworkAgent.sendLinkProperties(new LinkProperties(lp2));
+        mWiFiAgent.sendLinkProperties(new LinkProperties(lp2));
+        waitForIdle();
+        verify(mMockNetd, times(1)).interfaceSetMtu(eq(ifaceName2), eq(mtu));
+        verifyMtuNeverSetOnWifiInterface();
+    }
+
+    @Test
+    public void testCreateDeliveryGroupKeyForConnectivityAction() throws Exception {
+        final NetworkInfo info = new NetworkInfo(0 /* type */, 2 /* subtype */,
+                "MOBILE" /* typeName */, "LTE" /* subtypeName */);
+        assertEquals("0;2;null", createDeliveryGroupKeyForConnectivityAction(info));
+
+        info.setExtraInfo("test_info");
+        assertEquals("0;2;test_info", createDeliveryGroupKeyForConnectivityAction(info));
+    }
+
+    @Test
+    public void testNetdWakeupAddInterfaceForWifiTransport() throws Exception {
+        final LinkProperties wifiLp = new LinkProperties();
+        wifiLp.setInterfaceName(WIFI_IFNAME);
+        mWiFiAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        mWiFiAgent.connect(false /* validated */);
+
+        final String expectedPrefix = makeNflogPrefix(WIFI_IFNAME,
+                mWiFiAgent.getNetwork().getNetworkHandle());
+        verify(mMockNetd).wakeupAddInterface(WIFI_IFNAME, expectedPrefix, PACKET_WAKEUP_MARK,
+                PACKET_WAKEUP_MASK);
+    }
+
+    @Test
+    public void testNetdWakeupAddInterfaceForCellularTransport() throws Exception {
+        final LinkProperties cellLp = new LinkProperties();
+        cellLp.setInterfaceName(MOBILE_IFNAME);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
+        mCellAgent.connect(false /* validated */);
+
+        if (mDeps.isAtLeastU()) {
+            final String expectedPrefix = makeNflogPrefix(MOBILE_IFNAME,
+                    mCellAgent.getNetwork().getNetworkHandle());
+            verify(mMockNetd).wakeupAddInterface(MOBILE_IFNAME, expectedPrefix, PACKET_WAKEUP_MARK,
+                    PACKET_WAKEUP_MASK);
+        } else {
+            verify(mMockNetd, never()).wakeupAddInterface(eq(MOBILE_IFNAME), anyString(), anyInt(),
+                    anyInt());
+        }
+    }
+
+    @Test
+    public void testNetdWakeupAddInterfaceForEthernetTransport() throws Exception {
+        final String ethernetIface = "eth42";
+
+        final LinkProperties ethLp = new LinkProperties();
+        ethLp.setInterfaceName(ethernetIface);
+        mEthernetAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET, ethLp);
+        mEthernetAgent.connect(false /* validated */);
+
+        verify(mMockNetd, never()).wakeupAddInterface(eq(ethernetIface), anyString(), anyInt(),
+                anyInt());
+    }
+
+    private static final int TEST_FROZEN_UID = 1000;
+    private static final int TEST_UNFROZEN_UID = 2000;
+
+    /**
+     * Send a UidFrozenStateChanged message to ConnectivityService. Verify that only the frozen UID
+     * gets passed to socketDestroy().
+     */
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testFrozenUidSocketDestroy() throws Exception {
+        ArgumentCaptor<UidFrozenStateChangedCallback> callbackArg =
+                ArgumentCaptor.forClass(UidFrozenStateChangedCallback.class);
+
+        verify(mActivityManager).registerUidFrozenStateChangedCallback(any(),
+                callbackArg.capture());
+
+        final int[] uids = {TEST_FROZEN_UID, TEST_UNFROZEN_UID};
+        final int[] frozenStates = {UID_FROZEN_STATE_FROZEN, UID_FROZEN_STATE_UNFROZEN};
+
+        callbackArg.getValue().onUidFrozenStateChanged(uids, frozenStates);
 
         waitForIdle();
-        // TODO(b/246398088): the MTU should be set on the new interface.
-        verify(mMockNetd, never()).interfaceSetMtu(eq(ifaceName2), eq(mtu));
+
+        final Set<Integer> exemptUids = new ArraySet();
+        final UidRange frozenUidRange = new UidRange(TEST_FROZEN_UID, TEST_FROZEN_UID);
+        final Set<UidRange> ranges = Collections.singleton(frozenUidRange);
+
+        verify(mDestroySocketsWrapper).destroyLiveTcpSockets(eq(UidRange.toIntRanges(ranges)),
+                eq(exemptUids));
+    }
+
+    @Test
+    public void testDisconnectSuspendedNetworkStopClatd() throws Exception {
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_DUN)
+                .build();
+        mCm.requestNetwork(networkRequest, networkCallback);
+
+        final IpPrefix nat64Prefix = new IpPrefix(InetAddress.getByName("64:ff9b::"), 96);
+        NetworkCapabilities nc = new NetworkCapabilities().addCapability(NET_CAPABILITY_DUN);
+        final LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(MOBILE_IFNAME);
+        lp.addLinkAddress(new LinkAddress("2001:db8:1::1/64"));
+        lp.setNat64Prefix(nat64Prefix);
+        mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, lp, nc);
+        mCellAgent.connect(true /* validated */, false /* hasInternet */,
+                false /* privateDnsProbeSent */);
+
+        verifyClatdStart(null /* inOrder */, MOBILE_IFNAME, mCellAgent.getNetwork().netId,
+                nat64Prefix.toString());
+
+        mCellAgent.suspend();
+        mCm.unregisterNetworkCallback(networkCallback);
+        mCellAgent.expectDisconnected();
+        waitForIdle();
+
+        verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
     }
 }
diff --git a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
index 624071a..1618a62 100644
--- a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -23,6 +23,7 @@
 import static android.net.IpSecManager.DIRECTION_FWD;
 import static android.net.IpSecManager.DIRECTION_IN;
 import static android.net.IpSecManager.DIRECTION_OUT;
+import static android.net.IpSecManager.FEATURE_IPSEC_TUNNEL_MIGRATION;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.AF_INET6;
@@ -30,11 +31,16 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
+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.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -49,6 +55,7 @@
 import android.net.IpSecAlgorithm;
 import android.net.IpSecConfig;
 import android.net.IpSecManager;
+import android.net.IpSecMigrateInfoParcel;
 import android.net.IpSecSpiResponse;
 import android.net.IpSecTransform;
 import android.net.IpSecTransformResponse;
@@ -130,6 +137,9 @@
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F
     };
 
+    private static final String NEW_SRC_ADDRESS = "2001:db8:2::1";
+    private static final String NEW_DST_ADDRESS = "2001:db8:2::2";
+
     AppOpsManager mMockAppOps = mock(AppOpsManager.class);
     ConnectivityManager mMockConnectivityMgr = mock(ConnectivityManager.class);
 
@@ -369,8 +379,8 @@
                 .ipSecAddSecurityAssociation(
                         eq(mUid),
                         eq(config.getMode()),
-                        eq(config.getSourceAddress()),
-                        eq(config.getDestinationAddress()),
+                        eq(mSourceAddr),
+                        eq(mDestinationAddr),
                         eq((config.getNetwork() != null) ? config.getNetwork().netId : 0),
                         eq(TEST_SPI),
                         eq(0),
@@ -910,9 +920,60 @@
         }
     }
 
+    @Test
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testApplyAndMigrateTunnelModeTransformOutbound() throws Exception {
+        verifyApplyAndMigrateTunnelModeTransformCommon(false, DIRECTION_OUT);
+    }
+
+    @Test
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testApplyAndMigrateTunnelModeTransformOutboundReleasedSpi() throws Exception {
+        verifyApplyAndMigrateTunnelModeTransformCommon(true, DIRECTION_OUT);
+    }
+
+    @Test
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testApplyAndMigrateTunnelModeTransformInbound() throws Exception {
+        verifyApplyAndMigrateTunnelModeTransformCommon(false, DIRECTION_IN);
+    }
+
+    @Test
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testApplyAndMigrateTunnelModeTransformInboundReleasedSpi() throws Exception {
+        verifyApplyAndMigrateTunnelModeTransformCommon(true, DIRECTION_IN);
+    }
+
+    @Test
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testApplyAndMigrateTunnelModeTransformForward() throws Exception {
+        verifyApplyAndMigrateTunnelModeTransformCommon(false, DIRECTION_FWD);
+    }
+
+    @Test
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testApplyAndMigrateTunnelModeTransformForwardReleasedSpi() throws Exception {
+        verifyApplyAndMigrateTunnelModeTransformCommon(true, DIRECTION_FWD);
+    }
+
     public void verifyApplyTunnelModeTransformCommon(boolean closeSpiBeforeApply, int direction)
             throws Exception {
-        IpSecConfig ipSecConfig = new IpSecConfig();
+        verifyApplyTunnelModeTransformCommon(
+                new IpSecConfig(), closeSpiBeforeApply, false /* isMigrating */, direction);
+    }
+
+    public void verifyApplyAndMigrateTunnelModeTransformCommon(
+            boolean closeSpiBeforeApply, int direction) throws Exception {
+        verifyApplyTunnelModeTransformCommon(
+                new IpSecConfig(), closeSpiBeforeApply, true /* isMigrating */, direction);
+    }
+
+    public int verifyApplyTunnelModeTransformCommon(
+            IpSecConfig ipSecConfig,
+            boolean closeSpiBeforeApply,
+            boolean isMigrating,
+            int direction)
+            throws Exception {
         ipSecConfig.setMode(IpSecTransform.MODE_TUNNEL);
         addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
         addAuthAndCryptToIpSecConfig(ipSecConfig);
@@ -928,6 +989,12 @@
 
         int transformResourceId = createTransformResp.resourceId;
         int tunnelResourceId = createTunnelResp.resourceId;
+
+        if (isMigrating) {
+            mIpSecService.migrateTransform(
+                    transformResourceId, NEW_SRC_ADDRESS, NEW_DST_ADDRESS, BLESSED_PACKAGE);
+        }
+
         mIpSecService.applyTunnelModeTransform(
                 tunnelResourceId, direction, transformResourceId, BLESSED_PACKAGE);
 
@@ -947,8 +1014,16 @@
 
         ipSecConfig.setXfrmInterfaceId(tunnelResourceId);
         verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp);
-    }
 
+        if (isMigrating) {
+            verify(mMockNetd, times(ADDRESS_FAMILIES.length))
+                    .ipSecMigrate(any(IpSecMigrateInfoParcel.class));
+        } else {
+            verify(mMockNetd, never()).ipSecMigrate(any());
+        }
+
+        return tunnelResourceId;
+    }
 
     @Test
     public void testApplyTunnelModeTransformWithClosedSpi() throws Exception {
@@ -1023,7 +1098,7 @@
     }
 
     @Test
-    public void testFeatureFlagVerification() throws Exception {
+    public void testFeatureFlagIpSecTunnelsVerification() throws Exception {
         when(mMockPkgMgr.hasSystemFeature(eq(PackageManager.FEATURE_IPSEC_TUNNELS)))
                 .thenReturn(false);
 
@@ -1035,4 +1110,17 @@
         } catch (UnsupportedOperationException expected) {
         }
     }
+
+    @Test
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void testFeatureFlagIpSecTunnelMigrationVerification() throws Exception {
+        when(mMockPkgMgr.hasSystemFeature(eq(FEATURE_IPSEC_TUNNEL_MIGRATION))).thenReturn(false);
+
+        try {
+            mIpSecService.migrateTransform(
+                    1 /* transformId */, NEW_SRC_ADDRESS, NEW_DST_ADDRESS, BLESSED_PACKAGE);
+            fail("Expected UnsupportedOperationException for disabled feature");
+        } catch (UnsupportedOperationException expected) {
+        }
+    }
 }
diff --git a/tests/unit/java/com/android/server/IpSecServiceTest.java b/tests/unit/java/com/android/server/IpSecServiceTest.java
index 6955620..4b6857c 100644
--- a/tests/unit/java/com/android/server/IpSecServiceTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceTest.java
@@ -82,7 +82,7 @@
     private static final int MAX_NUM_ENCAP_SOCKETS = 100;
     private static final int MAX_NUM_SPIS = 100;
     private static final int TEST_UDP_ENCAP_INVALID_PORT = 100;
-    private static final int TEST_UDP_ENCAP_PORT_OUT_RANGE = 100000;
+    private static final int TEST_UDP_ENCAP_PORT_OUT_RANGE = 200000;
 
     private static final InetAddress INADDR_ANY;
 
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 5808beb..1997215 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -16,16 +16,28 @@
 
 package com.android.server;
 
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_PLATFORM_MDNS_BACKEND;
+import static android.net.connectivity.ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER;
+import static android.net.nsd.NsdManager.FAILURE_BAD_PARAMETERS;
 import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
+import static android.net.nsd.NsdManager.FAILURE_OPERATION_NOT_RUNNING;
+
+import static com.android.server.NsdService.parseTypeAndSubtype;
+import static com.android.testutils.ContextUtils.mockService;
 
 import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doReturn;
@@ -36,13 +48,14 @@
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.net.INetd;
-import android.net.InetAddresses;
+import android.net.Network;
 import android.net.mdns.aidl.DiscoveryInfo;
 import android.net.mdns.aidl.GetAddressInfo;
 import android.net.mdns.aidl.IMDnsEventListener;
@@ -63,10 +76,19 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
+import android.util.Pair;
 
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.NsdService.Dependencies;
+import com.android.server.connectivity.mdns.MdnsAdvertiser;
+import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
+import com.android.server.connectivity.mdns.MdnsSearchOptions;
+import com.android.server.connectivity.mdns.MdnsServiceBrowserListener;
+import com.android.server.connectivity.mdns.MdnsServiceInfo;
+import com.android.server.connectivity.mdns.MdnsSocketProvider;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 import com.android.testutils.HandlerUtils;
@@ -82,7 +104,12 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Collections;
 import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
 import java.util.Queue;
 
 // TODOs:
@@ -96,11 +123,13 @@
     private static final long CLEANUP_DELAY_MS = 500;
     private static final long TIMEOUT_MS = 500;
     private static final String SERVICE_NAME = "a_name";
-    private static final String SERVICE_TYPE = "a_type";
+    private static final String SERVICE_TYPE = "_test._tcp";
     private static final String SERVICE_FULL_NAME = SERVICE_NAME + "." + SERVICE_TYPE;
     private static final String DOMAIN_NAME = "mytestdevice.local";
     private static final int PORT = 2201;
     private static final int IFACE_IDX_ANY = 0;
+    private static final String IPV4_ADDRESS = "192.0.2.0";
+    private static final String IPV6_ADDRESS = "2001:db8::";
 
     // Records INsdManagerCallback created when NsdService#connect is called.
     // Only accessed on the test thread, since NsdService#connect is called by the NsdManager
@@ -112,6 +141,10 @@
     @Mock Context mContext;
     @Mock ContentResolver mResolver;
     @Mock MDnsManager mMockMDnsM;
+    @Mock Dependencies mDeps;
+    @Mock MdnsDiscoveryManager mDiscoveryManager;
+    @Mock MdnsAdvertiser mAdvertiser;
+    @Mock MdnsSocketProvider mSocketProvider;
     HandlerThread mThread;
     TestHandler mHandler;
     NsdService mService;
@@ -133,9 +166,7 @@
         mThread.start();
         mHandler = new TestHandler(mThread.getLooper());
         when(mContext.getContentResolver()).thenReturn(mResolver);
-        doReturn(MDnsManager.MDNS_SERVICE).when(mContext)
-                .getSystemServiceName(MDnsManager.class);
-        doReturn(mMockMDnsM).when(mContext).getSystemService(MDnsManager.MDNS_SERVICE);
+        mockService(mContext, MDnsManager.class, MDnsManager.MDNS_SERVICE, mMockMDnsM);
         if (mContext.getSystemService(MDnsManager.class) == null) {
             // Test is using mockito-extended
             doCallRealMethod().when(mContext).getSystemService(MDnsManager.class);
@@ -146,7 +177,11 @@
         doReturn(true).when(mMockMDnsM).discover(anyInt(), anyString(), anyInt());
         doReturn(true).when(mMockMDnsM).resolve(
                 anyInt(), anyString(), anyString(), anyString(), anyInt());
-
+        doReturn(false).when(mDeps).isMdnsDiscoveryManagerEnabled(any(Context.class));
+        doReturn(mDiscoveryManager).when(mDeps)
+                .makeMdnsDiscoveryManager(any(), any(), any());
+        doReturn(mSocketProvider).when(mDeps).makeMdnsSocketProvider(any(), any(), any());
+        doReturn(mAdvertiser).when(mDeps).makeMdnsAdvertiser(any(), any(), any(), any());
         mService = makeService();
     }
 
@@ -159,7 +194,9 @@
     }
 
     @Test
-    @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @DisableCompatChanges({
+            RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER,
+            ENABLE_PLATFORM_MDNS_BACKEND})
     public void testPreSClients() throws Exception {
         // Pre S client connected, the daemon should be started.
         connectClient(mService);
@@ -186,7 +223,8 @@
     }
 
     @Test
-    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @EnableCompatChanges(RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testNoDaemonStartedWhenClientsConnect() throws Exception {
         // Creating an NsdManager will not cause daemon startup.
         connectClient(mService);
@@ -220,7 +258,8 @@
     }
 
     @Test
-    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @EnableCompatChanges(RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testClientRequestsAreGCedAtDisconnection() throws Exception {
         final NsdManager client = connectClient(mService);
         final INsdManagerCallback cb1 = getCallback();
@@ -263,7 +302,8 @@
     }
 
     @Test
-    @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @EnableCompatChanges(RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER)
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testCleanupDelayNoRequestActive() throws Exception {
         final NsdManager client = connectClient(mService);
 
@@ -299,6 +339,7 @@
     }
 
     @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testDiscoverOnTetheringDownstream() throws Exception {
         final NsdManager client = connectClient(mService);
         final int interfaceIdx = 123;
@@ -382,13 +423,44 @@
         final NsdServiceInfo resolvedService = resInfoCaptor.getValue();
         assertEquals(SERVICE_NAME, resolvedService.getServiceName());
         assertEquals("." + SERVICE_TYPE, resolvedService.getServiceType());
-        assertEquals(InetAddresses.parseNumericAddress(serviceAddress), resolvedService.getHost());
+        assertEquals(parseNumericAddress(serviceAddress), resolvedService.getHost());
         assertEquals(servicePort, resolvedService.getPort());
         assertNull(resolvedService.getNetwork());
         assertEquals(interfaceIdx, resolvedService.getInterfaceIndex());
     }
 
     @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
+    public void testDiscoverOnBlackholeNetwork() throws Exception {
+        final NsdManager client = connectClient(mService);
+        final DiscoveryListener discListener = mock(DiscoveryListener.class);
+        client.discoverServices(SERVICE_TYPE, PROTOCOL, discListener);
+        waitForIdle();
+
+        final IMDnsEventListener eventListener = getEventListener();
+        final ArgumentCaptor<Integer> discIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mMockMDnsM).discover(discIdCaptor.capture(), eq(SERVICE_TYPE),
+                eq(0) /* interfaceIdx */);
+        // NsdManager uses a separate HandlerThread to dispatch callbacks (on ServiceHandler), so
+        // this needs to use a timeout
+        verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStarted(SERVICE_TYPE);
+
+        final DiscoveryInfo discoveryInfo = new DiscoveryInfo(
+                discIdCaptor.getValue(),
+                IMDnsEventListener.SERVICE_FOUND,
+                SERVICE_NAME,
+                SERVICE_TYPE,
+                DOMAIN_NAME,
+                123 /* interfaceIdx */,
+                INetd.DUMMY_NET_ID); // netId of the blackhole network
+        eventListener.onServiceDiscoveryStatus(discoveryInfo);
+        waitForIdle();
+
+        verify(discListener, never()).onServiceFound(any());
+    }
+
+    @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testServiceRegistrationSuccessfulAndFailed() throws Exception {
         final NsdManager client = connectClient(mService);
         final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
@@ -435,6 +507,7 @@
     }
 
     @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testServiceDiscoveryFailed() throws Exception {
         final NsdManager client = connectClient(mService);
         final DiscoveryListener discListener = mock(DiscoveryListener.class);
@@ -461,6 +534,7 @@
     }
 
     @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testServiceResolutionFailed() throws Exception {
         final NsdManager client = connectClient(mService);
         final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
@@ -491,6 +565,7 @@
     }
 
     @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testGettingAddressFailed() throws Exception {
         final NsdManager client = connectClient(mService);
         final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
@@ -537,6 +612,7 @@
     }
 
     @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
     public void testNoCrashWhenProcessResolutionAfterBinderDied() throws Exception {
         final NsdManager client = connectClient(mService);
         final INsdManagerCallback cb = getCallback();
@@ -555,14 +631,720 @@
                 anyInt()/* interfaceIdx */);
     }
 
+    @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
+    public void testStopServiceResolution() {
+        final NsdManager client = connectClient(mService);
+        final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+        final ResolveListener resolveListener = mock(ResolveListener.class);
+        client.resolveService(request, resolveListener);
+        waitForIdle();
+
+        final ArgumentCaptor<Integer> resolvIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mMockMDnsM).resolve(resolvIdCaptor.capture(), eq(SERVICE_NAME), eq(SERVICE_TYPE),
+                eq("local.") /* domain */, eq(IFACE_IDX_ANY));
+
+        final int resolveId = resolvIdCaptor.getValue();
+        client.stopServiceResolution(resolveListener);
+        waitForIdle();
+
+        verify(mMockMDnsM).stopOperation(resolveId);
+        verify(resolveListener, timeout(TIMEOUT_MS)).onResolutionStopped(argThat(ns ->
+                request.getServiceName().equals(ns.getServiceName())
+                        && request.getServiceType().equals(ns.getServiceType())));
+    }
+
+    @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
+    public void testStopResolutionFailed() {
+        final NsdManager client = connectClient(mService);
+        final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+        final ResolveListener resolveListener = mock(ResolveListener.class);
+        client.resolveService(request, resolveListener);
+        waitForIdle();
+
+        final ArgumentCaptor<Integer> resolvIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mMockMDnsM).resolve(resolvIdCaptor.capture(), eq(SERVICE_NAME), eq(SERVICE_TYPE),
+                eq("local.") /* domain */, eq(IFACE_IDX_ANY));
+
+        final int resolveId = resolvIdCaptor.getValue();
+        doReturn(false).when(mMockMDnsM).stopOperation(anyInt());
+        client.stopServiceResolution(resolveListener);
+        waitForIdle();
+
+        verify(mMockMDnsM).stopOperation(resolveId);
+        verify(resolveListener, timeout(TIMEOUT_MS)).onStopResolutionFailed(argThat(ns ->
+                        request.getServiceName().equals(ns.getServiceName())
+                                && request.getServiceType().equals(ns.getServiceType())),
+                eq(FAILURE_OPERATION_NOT_RUNNING));
+    }
+
+    @Test @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
+    public void testStopResolutionDuringGettingAddress() throws RemoteException {
+        final NsdManager client = connectClient(mService);
+        final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+        final ResolveListener resolveListener = mock(ResolveListener.class);
+        client.resolveService(request, resolveListener);
+        waitForIdle();
+
+        final IMDnsEventListener eventListener = getEventListener();
+        final ArgumentCaptor<Integer> resolvIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mMockMDnsM).resolve(resolvIdCaptor.capture(), eq(SERVICE_NAME), eq(SERVICE_TYPE),
+                eq("local.") /* domain */, eq(IFACE_IDX_ANY));
+
+        // Resolve service successfully.
+        final ResolutionInfo resolutionInfo = new ResolutionInfo(
+                resolvIdCaptor.getValue(),
+                IMDnsEventListener.SERVICE_RESOLVED,
+                null /* serviceName */,
+                null /* serviceType */,
+                null /* domain */,
+                SERVICE_FULL_NAME,
+                DOMAIN_NAME,
+                PORT,
+                new byte[0] /* txtRecord */,
+                IFACE_IDX_ANY);
+        doReturn(true).when(mMockMDnsM).getServiceAddress(anyInt(), any(), anyInt());
+        eventListener.onServiceResolutionStatus(resolutionInfo);
+        waitForIdle();
+
+        final ArgumentCaptor<Integer> getAddrIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mMockMDnsM).getServiceAddress(getAddrIdCaptor.capture(), eq(DOMAIN_NAME),
+                eq(IFACE_IDX_ANY));
+
+        final int getAddrId = getAddrIdCaptor.getValue();
+        client.stopServiceResolution(resolveListener);
+        waitForIdle();
+
+        verify(mMockMDnsM).stopOperation(getAddrId);
+        verify(resolveListener, timeout(TIMEOUT_MS)).onResolutionStopped(argThat(ns ->
+                request.getServiceName().equals(ns.getServiceName())
+                        && request.getServiceType().equals(ns.getServiceType())));
+    }
+
+    private void verifyUpdatedServiceInfo(NsdServiceInfo info, String serviceName,
+            String serviceType, List<InetAddress> address, int port, int interfaceIndex,
+            Network network) {
+        assertEquals(serviceName, info.getServiceName());
+        assertEquals(serviceType, info.getServiceType());
+        assertEquals(address, info.getHostAddresses());
+        assertEquals(port, info.getPort());
+        assertEquals(network, info.getNetwork());
+        assertEquals(interfaceIndex, info.getInterfaceIndex());
+    }
+
+    @Test
+    public void testRegisterAndUnregisterServiceInfoCallback() {
+        final NsdManager client = connectClient(mService);
+        final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+        final NsdManager.ServiceInfoCallback serviceInfoCallback = mock(
+                NsdManager.ServiceInfoCallback.class);
+        final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
+        final Network network = new Network(999);
+        request.setNetwork(network);
+        client.registerServiceInfoCallback(request, Runnable::run, serviceInfoCallback);
+        waitForIdle();
+        // Verify the registration callback start.
+        final ArgumentCaptor<MdnsServiceBrowserListener> listenerCaptor =
+                ArgumentCaptor.forClass(MdnsServiceBrowserListener.class);
+        verify(mSocketProvider).startMonitoringSockets();
+        verify(mDiscoveryManager).registerListener(eq(serviceTypeWithLocalDomain),
+                listenerCaptor.capture(), argThat(options -> network.equals(options.getNetwork())));
+
+        final MdnsServiceBrowserListener listener = listenerCaptor.getValue();
+        final MdnsServiceInfo mdnsServiceInfo = new MdnsServiceInfo(
+                SERVICE_NAME,
+                serviceTypeWithLocalDomain.split("\\."),
+                List.of(), /* subtypes */
+                new String[]{"android", "local"}, /* hostName */
+                PORT,
+                List.of(IPV4_ADDRESS),
+                List.of(IPV6_ADDRESS),
+                List.of() /* textStrings */,
+                List.of() /* textEntries */,
+                1234,
+                network);
+
+        // Verify onServiceFound callback
+        listener.onServiceFound(mdnsServiceInfo);
+        final ArgumentCaptor<NsdServiceInfo> updateInfoCaptor =
+                ArgumentCaptor.forClass(NsdServiceInfo.class);
+        verify(serviceInfoCallback, timeout(TIMEOUT_MS).times(1))
+                .onServiceUpdated(updateInfoCaptor.capture());
+        verifyUpdatedServiceInfo(updateInfoCaptor.getAllValues().get(0) /* info */, SERVICE_NAME,
+                SERVICE_TYPE,
+                List.of(parseNumericAddress(IPV4_ADDRESS), parseNumericAddress(IPV6_ADDRESS)),
+                PORT, IFACE_IDX_ANY, new Network(999));
+
+        // Service addresses changed.
+        final String v4Address = "192.0.2.1";
+        final String v6Address = "2001:db8::1";
+        final MdnsServiceInfo updatedServiceInfo = new MdnsServiceInfo(
+                SERVICE_NAME,
+                serviceTypeWithLocalDomain.split("\\."),
+                List.of(), /* subtypes */
+                new String[]{"android", "local"}, /* hostName */
+                PORT,
+                List.of(v4Address),
+                List.of(v6Address),
+                List.of() /* textStrings */,
+                List.of() /* textEntries */,
+                1234,
+                network);
+
+        // Verify onServiceUpdated callback.
+        listener.onServiceUpdated(updatedServiceInfo);
+        verify(serviceInfoCallback, timeout(TIMEOUT_MS).times(2))
+                .onServiceUpdated(updateInfoCaptor.capture());
+        verifyUpdatedServiceInfo(updateInfoCaptor.getAllValues().get(2) /* info */, SERVICE_NAME,
+                SERVICE_TYPE,
+                List.of(parseNumericAddress(v4Address), parseNumericAddress(v6Address)),
+                PORT, IFACE_IDX_ANY, new Network(999));
+
+        // Verify service callback unregistration.
+        client.unregisterServiceInfoCallback(serviceInfoCallback);
+        waitForIdle();
+        verify(serviceInfoCallback, timeout(TIMEOUT_MS)).onServiceInfoCallbackUnregistered();
+    }
+
+    @Test
+    public void testRegisterServiceCallbackFailed() {
+        final NsdManager client = connectClient(mService);
+        final String invalidServiceType = "a_service";
+        final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, invalidServiceType);
+        final NsdManager.ServiceInfoCallback serviceInfoCallback = mock(
+                NsdManager.ServiceInfoCallback.class);
+        client.registerServiceInfoCallback(request, Runnable::run, serviceInfoCallback);
+        waitForIdle();
+
+        // Fail to register service callback.
+        verify(serviceInfoCallback, timeout(TIMEOUT_MS))
+                .onServiceInfoCallbackRegistrationFailed(eq(FAILURE_BAD_PARAMETERS));
+    }
+
+    @Test
+    public void testUnregisterNotRegisteredCallback() {
+        final NsdManager client = connectClient(mService);
+        final NsdManager.ServiceInfoCallback serviceInfoCallback = mock(
+                NsdManager.ServiceInfoCallback.class);
+
+        assertThrows(IllegalArgumentException.class, () ->
+                client.unregisterServiceInfoCallback(serviceInfoCallback));
+    }
+
+    private void setMdnsDiscoveryManagerEnabled() {
+        doReturn(true).when(mDeps).isMdnsDiscoveryManagerEnabled(any(Context.class));
+    }
+
+    private void setMdnsAdvertiserEnabled() {
+        doReturn(true).when(mDeps).isMdnsAdvertiserEnabled(any(Context.class));
+    }
+
+    @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
+    public void testMdnsDiscoveryManagerFeature() {
+        // Create NsdService w/o feature enabled.
+        final NsdManager client = connectClient(mService);
+        final DiscoveryListener discListenerWithoutFeature = mock(DiscoveryListener.class);
+        client.discoverServices(SERVICE_TYPE, PROTOCOL, discListenerWithoutFeature);
+        waitForIdle();
+
+        final ArgumentCaptor<Integer> legacyIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mMockMDnsM).discover(legacyIdCaptor.capture(), any(), anyInt());
+        verifyNoMoreInteractions(mDiscoveryManager);
+
+        setMdnsDiscoveryManagerEnabled();
+        final DiscoveryListener discListenerWithFeature = mock(DiscoveryListener.class);
+        client.discoverServices(SERVICE_TYPE, PROTOCOL, discListenerWithFeature);
+        waitForIdle();
+
+        final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
+        final ArgumentCaptor<MdnsServiceBrowserListener> listenerCaptor =
+                ArgumentCaptor.forClass(MdnsServiceBrowserListener.class);
+        verify(mDiscoveryManager).registerListener(eq(serviceTypeWithLocalDomain),
+                listenerCaptor.capture(), any());
+
+        client.stopServiceDiscovery(discListenerWithoutFeature);
+        waitForIdle();
+        verify(mMockMDnsM).stopOperation(legacyIdCaptor.getValue());
+
+        client.stopServiceDiscovery(discListenerWithFeature);
+        waitForIdle();
+        verify(mDiscoveryManager).unregisterListener(serviceTypeWithLocalDomain,
+                listenerCaptor.getValue());
+    }
+
+    @Test
+    public void testDiscoveryWithMdnsDiscoveryManager() {
+        setMdnsDiscoveryManagerEnabled();
+
+        final NsdManager client = connectClient(mService);
+        final DiscoveryListener discListener = mock(DiscoveryListener.class);
+        final Network network = new Network(999);
+        final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
+        // Verify the discovery start / stop.
+        final ArgumentCaptor<MdnsServiceBrowserListener> listenerCaptor =
+                ArgumentCaptor.forClass(MdnsServiceBrowserListener.class);
+        client.discoverServices(SERVICE_TYPE, PROTOCOL, network, r -> r.run(), discListener);
+        waitForIdle();
+        verify(mSocketProvider).startMonitoringSockets();
+        verify(mDiscoveryManager).registerListener(eq(serviceTypeWithLocalDomain),
+                listenerCaptor.capture(), argThat(options -> network.equals(options.getNetwork())));
+        verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStarted(SERVICE_TYPE);
+
+        final MdnsServiceBrowserListener listener = listenerCaptor.getValue();
+        final MdnsServiceInfo foundInfo = new MdnsServiceInfo(
+                SERVICE_NAME, /* serviceInstanceName */
+                serviceTypeWithLocalDomain.split("\\."), /* serviceType */
+                List.of(), /* subtypes */
+                new String[] {"android", "local"}, /* hostName */
+                12345, /* port */
+                List.of(IPV4_ADDRESS),
+                List.of(IPV6_ADDRESS),
+                List.of(), /* textStrings */
+                List.of(), /* textEntries */
+                1234, /* interfaceIndex */
+                network);
+
+        // Verify onServiceNameDiscovered callback
+        listener.onServiceNameDiscovered(foundInfo);
+        verify(discListener, timeout(TIMEOUT_MS)).onServiceFound(argThat(info ->
+                info.getServiceName().equals(SERVICE_NAME)
+                        // Service type in discovery callbacks has a dot at the end
+                        && info.getServiceType().equals(SERVICE_TYPE + ".")
+                        && info.getNetwork().equals(network)));
+
+        final MdnsServiceInfo removedInfo = new MdnsServiceInfo(
+                SERVICE_NAME, /* serviceInstanceName */
+                serviceTypeWithLocalDomain.split("\\."), /* serviceType */
+                null, /* subtypes */
+                null, /* hostName */
+                0, /* port */
+                List.of(), /* ipv4Address */
+                List.of(), /* ipv6Address */
+                null, /* textStrings */
+                null, /* textEntries */
+                1234, /* interfaceIndex */
+                network);
+        // Verify onServiceNameRemoved callback
+        listener.onServiceNameRemoved(removedInfo);
+        verify(discListener, timeout(TIMEOUT_MS)).onServiceLost(argThat(info ->
+                info.getServiceName().equals(SERVICE_NAME)
+                        // Service type in discovery callbacks has a dot at the end
+                        && info.getServiceType().equals(SERVICE_TYPE + ".")
+                        && info.getNetwork().equals(network)));
+
+        client.stopServiceDiscovery(discListener);
+        waitForIdle();
+        verify(mDiscoveryManager).unregisterListener(eq(serviceTypeWithLocalDomain), any());
+        verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStopped(SERVICE_TYPE);
+        verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).requestStopWhenInactive();
+    }
+
+    @Test
+    public void testDiscoveryWithMdnsDiscoveryManager_FailedWithInvalidServiceType() {
+        setMdnsDiscoveryManagerEnabled();
+
+        final NsdManager client = connectClient(mService);
+        final DiscoveryListener discListener = mock(DiscoveryListener.class);
+        final Network network = new Network(999);
+        final String invalidServiceType = "a_service";
+        client.discoverServices(
+                invalidServiceType, PROTOCOL, network, r -> r.run(), discListener);
+        waitForIdle();
+        verify(discListener, timeout(TIMEOUT_MS))
+                .onStartDiscoveryFailed(invalidServiceType, FAILURE_INTERNAL_ERROR);
+
+        final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
+        client.discoverServices(
+                serviceTypeWithLocalDomain, PROTOCOL, network, r -> r.run(), discListener);
+        waitForIdle();
+        verify(discListener, timeout(TIMEOUT_MS))
+                .onStartDiscoveryFailed(serviceTypeWithLocalDomain, FAILURE_INTERNAL_ERROR);
+
+        final String serviceTypeWithoutTcpOrUdpEnding = "_test._com";
+        client.discoverServices(
+                serviceTypeWithoutTcpOrUdpEnding, PROTOCOL, network, r -> r.run(), discListener);
+        waitForIdle();
+        verify(discListener, timeout(TIMEOUT_MS))
+                .onStartDiscoveryFailed(serviceTypeWithoutTcpOrUdpEnding, FAILURE_INTERNAL_ERROR);
+    }
+
+    @Test
+    @EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
+    public void testDiscoveryWithMdnsDiscoveryManager_UsesSubtypes() {
+        final String typeWithSubtype = SERVICE_TYPE + ",_subtype";
+        final NsdManager client = connectClient(mService);
+        final NsdServiceInfo regInfo = new NsdServiceInfo("Instance", typeWithSubtype);
+        final Network network = new Network(999);
+        regInfo.setHostAddresses(List.of(parseNumericAddress("192.0.2.123")));
+        regInfo.setPort(12345);
+        regInfo.setNetwork(network);
+
+        final RegistrationListener regListener = mock(RegistrationListener.class);
+        client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
+        waitForIdle();
+        verify(mAdvertiser).addService(anyInt(), argThat(s ->
+                "Instance".equals(s.getServiceName())
+                        && SERVICE_TYPE.equals(s.getServiceType())), eq("_subtype"));
+
+        final DiscoveryListener discListener = mock(DiscoveryListener.class);
+        client.discoverServices(typeWithSubtype, PROTOCOL, network, Runnable::run, discListener);
+        waitForIdle();
+        final ArgumentCaptor<MdnsSearchOptions> optionsCaptor =
+                ArgumentCaptor.forClass(MdnsSearchOptions.class);
+        verify(mDiscoveryManager).registerListener(eq(SERVICE_TYPE + ".local"), any(),
+                optionsCaptor.capture());
+        assertEquals(Collections.singletonList("subtype"), optionsCaptor.getValue().getSubtypes());
+    }
+
+    @Test
+    public void testResolutionWithMdnsDiscoveryManager() throws UnknownHostException {
+        setMdnsDiscoveryManagerEnabled();
+
+        final NsdManager client = connectClient(mService);
+        final ResolveListener resolveListener = mock(ResolveListener.class);
+        final Network network = new Network(999);
+        final String serviceType = "_nsd._service._tcp";
+        final String constructedServiceType = "_service._tcp.local";
+        final ArgumentCaptor<MdnsServiceBrowserListener> listenerCaptor =
+                ArgumentCaptor.forClass(MdnsServiceBrowserListener.class);
+        final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, serviceType);
+        request.setNetwork(network);
+        client.resolveService(request, resolveListener);
+        waitForIdle();
+        verify(mSocketProvider).startMonitoringSockets();
+        final ArgumentCaptor<MdnsSearchOptions> optionsCaptor =
+                ArgumentCaptor.forClass(MdnsSearchOptions.class);
+        verify(mDiscoveryManager).registerListener(eq(constructedServiceType),
+                listenerCaptor.capture(),
+                optionsCaptor.capture());
+        assertEquals(network, optionsCaptor.getValue().getNetwork());
+        // Subtypes are not used for resolution, only for discovery
+        assertEquals(Collections.emptyList(), optionsCaptor.getValue().getSubtypes());
+
+        final MdnsServiceBrowserListener listener = listenerCaptor.getValue();
+        final MdnsServiceInfo mdnsServiceInfo = new MdnsServiceInfo(
+                SERVICE_NAME,
+                constructedServiceType.split("\\."),
+                List.of(), /* subtypes */
+                new String[]{"android", "local"}, /* hostName */
+                PORT,
+                List.of(IPV4_ADDRESS),
+                List.of("2001:db8::1", "2001:db8::2"),
+                List.of() /* textStrings */,
+                List.of(MdnsServiceInfo.TextEntry.fromBytes(new byte[]{
+                        'k', 'e', 'y', '=', (byte) 0xFF, (byte) 0xFE})) /* textEntries */,
+                1234,
+                network);
+
+        // Verify onServiceFound callback
+        listener.onServiceFound(mdnsServiceInfo);
+        final ArgumentCaptor<NsdServiceInfo> infoCaptor =
+                ArgumentCaptor.forClass(NsdServiceInfo.class);
+        verify(resolveListener, timeout(TIMEOUT_MS)).onServiceResolved(infoCaptor.capture());
+        final NsdServiceInfo info = infoCaptor.getValue();
+        assertEquals(SERVICE_NAME, info.getServiceName());
+        assertEquals("._service._tcp", info.getServiceType());
+        assertEquals(PORT, info.getPort());
+        assertTrue(info.getAttributes().containsKey("key"));
+        assertEquals(1, info.getAttributes().size());
+        assertArrayEquals(new byte[]{(byte) 0xFF, (byte) 0xFE}, info.getAttributes().get("key"));
+        assertEquals(parseNumericAddress(IPV4_ADDRESS), info.getHost());
+        assertEquals(3, info.getHostAddresses().size());
+        assertTrue(info.getHostAddresses().stream().anyMatch(
+                address -> address.equals(parseNumericAddress("2001:db8::1"))));
+        assertTrue(info.getHostAddresses().stream().anyMatch(
+                address -> address.equals(parseNumericAddress("2001:db8::2"))));
+        assertEquals(network, info.getNetwork());
+
+        // Verify the listener has been unregistered.
+        verify(mDiscoveryManager, timeout(TIMEOUT_MS))
+                .unregisterListener(eq(constructedServiceType), any());
+        verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).requestStopWhenInactive();
+    }
+
+    @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
+    public void testMdnsAdvertiserFeatureFlagging() {
+        // Create NsdService w/o feature enabled.
+        final NsdManager client = connectClient(mService);
+        final NsdServiceInfo regInfo = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+        regInfo.setHost(parseNumericAddress("192.0.2.123"));
+        regInfo.setPort(12345);
+        final RegistrationListener regListenerWithoutFeature = mock(RegistrationListener.class);
+        client.registerService(regInfo, PROTOCOL, regListenerWithoutFeature);
+        waitForIdle();
+
+        final ArgumentCaptor<Integer> legacyIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mMockMDnsM).registerService(legacyIdCaptor.capture(), any(), any(), anyInt(),
+                any(), anyInt());
+        verifyNoMoreInteractions(mAdvertiser);
+
+        setMdnsAdvertiserEnabled();
+        final RegistrationListener regListenerWithFeature = mock(RegistrationListener.class);
+        client.registerService(regInfo, PROTOCOL, regListenerWithFeature);
+        waitForIdle();
+
+        final ArgumentCaptor<Integer> serviceIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mAdvertiser).addService(serviceIdCaptor.capture(),
+                argThat(info -> matches(info, regInfo)), eq(null) /* subtype */);
+
+        client.unregisterService(regListenerWithoutFeature);
+        waitForIdle();
+        verify(mMockMDnsM).stopOperation(legacyIdCaptor.getValue());
+        verify(mAdvertiser, never()).removeService(anyInt());
+
+        client.unregisterService(regListenerWithFeature);
+        waitForIdle();
+        verify(mAdvertiser).removeService(serviceIdCaptor.getValue());
+    }
+
+    @Test
+    @DisableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
+    public void testTypeSpecificFeatureFlagging() {
+        doReturn("_type1._tcp:flag1,_type2._tcp:flag2").when(mDeps).getTypeAllowlistFlags();
+        doReturn(true).when(mDeps).isFeatureEnabled(any(),
+                eq("mdns_discovery_manager_allowlist_flag1_version"));
+        doReturn(true).when(mDeps).isFeatureEnabled(any(),
+                eq("mdns_advertiser_allowlist_flag2_version"));
+
+        final NsdManager client = connectClient(mService);
+        final NsdServiceInfo service1 = new NsdServiceInfo(SERVICE_NAME, "_type1._tcp");
+        service1.setHostAddresses(List.of(parseNumericAddress("2001:db8::123")));
+        service1.setPort(1234);
+        final NsdServiceInfo service2 = new NsdServiceInfo(SERVICE_NAME, "_type2._tcp");
+        service2.setHostAddresses(List.of(parseNumericAddress("2001:db8::123")));
+        service2.setPort(1234);
+
+        client.discoverServices(service1.getServiceType(),
+                NsdManager.PROTOCOL_DNS_SD, mock(DiscoveryListener.class));
+        client.discoverServices(service2.getServiceType(),
+                NsdManager.PROTOCOL_DNS_SD, mock(DiscoveryListener.class));
+        waitForIdle();
+
+        // The DiscoveryManager is enabled for _type1 but not _type2
+        verify(mDiscoveryManager).registerListener(eq("_type1._tcp.local"), any(), any());
+        verify(mDiscoveryManager, never()).registerListener(
+                eq("_type2._tcp.local"), any(), any());
+
+        client.resolveService(service1, mock(ResolveListener.class));
+        client.resolveService(service2, mock(ResolveListener.class));
+        waitForIdle();
+
+        // Same behavior for resolve
+        verify(mDiscoveryManager, times(2)).registerListener(
+                eq("_type1._tcp.local"), any(), any());
+        verify(mDiscoveryManager, never()).registerListener(
+                eq("_type2._tcp.local"), any(), any());
+
+        client.registerService(service1, NsdManager.PROTOCOL_DNS_SD,
+                mock(RegistrationListener.class));
+        client.registerService(service2, NsdManager.PROTOCOL_DNS_SD,
+                mock(RegistrationListener.class));
+        waitForIdle();
+
+        // The advertiser is enabled for _type2 but not _type1
+        verify(mAdvertiser, never()).addService(
+                anyInt(), argThat(info -> matches(info, service1)), eq(null) /* subtype */);
+        verify(mAdvertiser).addService(
+                anyInt(), argThat(info -> matches(info, service2)), eq(null) /* subtype */);
+    }
+
+    @Test
+    public void testAdvertiseWithMdnsAdvertiser() {
+        setMdnsAdvertiserEnabled();
+
+        final NsdManager client = connectClient(mService);
+        final RegistrationListener regListener = mock(RegistrationListener.class);
+        // final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
+        final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
+                ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
+        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any());
+
+        final NsdServiceInfo regInfo = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);
+        regInfo.setHost(parseNumericAddress("192.0.2.123"));
+        regInfo.setPort(12345);
+        regInfo.setAttribute("testattr", "testvalue");
+        regInfo.setNetwork(new Network(999));
+
+        client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
+        waitForIdle();
+        verify(mSocketProvider).startMonitoringSockets();
+        final ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mAdvertiser).addService(idCaptor.capture(), argThat(info ->
+                matches(info, regInfo)), eq(null) /* subtype */);
+
+        // Verify onServiceRegistered callback
+        final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
+        cb.onRegisterServiceSucceeded(idCaptor.getValue(), regInfo);
+
+        verify(regListener, timeout(TIMEOUT_MS)).onServiceRegistered(argThat(info -> matches(info,
+                new NsdServiceInfo(regInfo.getServiceName(), null))));
+
+        client.unregisterService(regListener);
+        waitForIdle();
+        verify(mAdvertiser).removeService(idCaptor.getValue());
+        verify(regListener, timeout(TIMEOUT_MS)).onServiceUnregistered(
+                argThat(info -> matches(info, regInfo)));
+        verify(mSocketProvider, timeout(TIMEOUT_MS)).requestStopWhenInactive();
+    }
+
+    @Test
+    public void testAdvertiseWithMdnsAdvertiser_FailedWithInvalidServiceType() {
+        setMdnsAdvertiserEnabled();
+
+        final NsdManager client = connectClient(mService);
+        final RegistrationListener regListener = mock(RegistrationListener.class);
+        // final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
+        final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
+                ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
+        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any());
+
+        final NsdServiceInfo regInfo = new NsdServiceInfo(SERVICE_NAME, "invalid_type");
+        regInfo.setHost(parseNumericAddress("192.0.2.123"));
+        regInfo.setPort(12345);
+        regInfo.setAttribute("testattr", "testvalue");
+        regInfo.setNetwork(new Network(999));
+
+        client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
+        waitForIdle();
+        verify(mAdvertiser, never()).addService(anyInt(), any(), any());
+
+        verify(regListener, timeout(TIMEOUT_MS)).onRegistrationFailed(
+                argThat(info -> matches(info, regInfo)), eq(FAILURE_INTERNAL_ERROR));
+    }
+
+    @Test
+    public void testAdvertiseWithMdnsAdvertiser_LongServiceName() {
+        setMdnsAdvertiserEnabled();
+
+        final NsdManager client = connectClient(mService);
+        final RegistrationListener regListener = mock(RegistrationListener.class);
+        // final String serviceTypeWithLocalDomain = SERVICE_TYPE + ".local";
+        final ArgumentCaptor<MdnsAdvertiser.AdvertiserCallback> cbCaptor =
+                ArgumentCaptor.forClass(MdnsAdvertiser.AdvertiserCallback.class);
+        verify(mDeps).makeMdnsAdvertiser(any(), any(), cbCaptor.capture(), any());
+
+        final NsdServiceInfo regInfo = new NsdServiceInfo("a".repeat(70), SERVICE_TYPE);
+        regInfo.setHost(parseNumericAddress("192.0.2.123"));
+        regInfo.setPort(12345);
+        regInfo.setAttribute("testattr", "testvalue");
+        regInfo.setNetwork(new Network(999));
+
+        client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
+        waitForIdle();
+        final ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
+        // Service name is truncated to 63 characters
+        verify(mAdvertiser).addService(idCaptor.capture(),
+                argThat(info -> info.getServiceName().equals("a".repeat(63))),
+                eq(null) /* subtype */);
+
+        // Verify onServiceRegistered callback
+        final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
+        cb.onRegisterServiceSucceeded(idCaptor.getValue(), regInfo);
+
+        verify(regListener, timeout(TIMEOUT_MS)).onServiceRegistered(
+                argThat(info -> matches(info, new NsdServiceInfo(regInfo.getServiceName(), null))));
+    }
+
+    @Test
+    public void testStopServiceResolutionWithMdnsDiscoveryManager() {
+        setMdnsDiscoveryManagerEnabled();
+
+        final NsdManager client = connectClient(mService);
+        final ResolveListener resolveListener = mock(ResolveListener.class);
+        final Network network = new Network(999);
+        final String serviceType = "_nsd._service._tcp";
+        final String constructedServiceType = "_service._tcp.local";
+        final ArgumentCaptor<MdnsServiceBrowserListener> listenerCaptor =
+                ArgumentCaptor.forClass(MdnsServiceBrowserListener.class);
+        final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, serviceType);
+        request.setNetwork(network);
+        client.resolveService(request, resolveListener);
+        waitForIdle();
+        verify(mSocketProvider).startMonitoringSockets();
+        final ArgumentCaptor<MdnsSearchOptions> optionsCaptor =
+                ArgumentCaptor.forClass(MdnsSearchOptions.class);
+        verify(mDiscoveryManager).registerListener(eq(constructedServiceType),
+                listenerCaptor.capture(),
+                optionsCaptor.capture());
+        assertEquals(network, optionsCaptor.getValue().getNetwork());
+        // Subtypes are not used for resolution, only for discovery
+        assertEquals(Collections.emptyList(), optionsCaptor.getValue().getSubtypes());
+
+        client.stopServiceResolution(resolveListener);
+        waitForIdle();
+
+        // Verify the listener has been unregistered.
+        verify(mDiscoveryManager, timeout(TIMEOUT_MS))
+                .unregisterListener(eq(constructedServiceType), eq(listenerCaptor.getValue()));
+        verify(resolveListener, timeout(TIMEOUT_MS)).onResolutionStopped(argThat(ns ->
+                request.getServiceName().equals(ns.getServiceName())
+                        && request.getServiceType().equals(ns.getServiceType())));
+        verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).requestStopWhenInactive();
+    }
+
+    @Test
+    public void testParseTypeAndSubtype() {
+        final String serviceType1 = "test._tcp";
+        final String serviceType2 = "_test._quic";
+        final String serviceType3 = "_test._quic,_test1,_test2";
+        final String serviceType4 = "_123._udp.";
+        final String serviceType5 = "_TEST._999._tcp.";
+        final String serviceType6 = "_998._tcp.,_TEST";
+        final String serviceType7 = "_997._tcp,_TEST";
+
+        assertNull(parseTypeAndSubtype(serviceType1));
+        assertNull(parseTypeAndSubtype(serviceType2));
+        assertNull(parseTypeAndSubtype(serviceType3));
+        assertEquals(new Pair<>("_123._udp", null), parseTypeAndSubtype(serviceType4));
+        assertEquals(new Pair<>("_999._tcp", "_TEST"), parseTypeAndSubtype(serviceType5));
+        assertEquals(new Pair<>("_998._tcp", "_TEST"), parseTypeAndSubtype(serviceType6));
+        assertEquals(new Pair<>("_997._tcp", "_TEST"), parseTypeAndSubtype(serviceType7));
+    }
+
+    @Test
+    @EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
+    public void testEnablePlatformMdnsBackend() {
+        final NsdManager client = connectClient(mService);
+        final NsdServiceInfo regInfo = new NsdServiceInfo("a".repeat(70), SERVICE_TYPE);
+        final Network network = new Network(999);
+        regInfo.setHostAddresses(List.of(parseNumericAddress("192.0.2.123")));
+        regInfo.setPort(12345);
+        regInfo.setAttribute("testattr", "testvalue");
+        regInfo.setNetwork(network);
+
+        // Verify the registration uses MdnsAdvertiser
+        final RegistrationListener regListener = mock(RegistrationListener.class);
+        client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
+        waitForIdle();
+        verify(mSocketProvider).startMonitoringSockets();
+        verify(mAdvertiser).addService(anyInt(), any(), any());
+
+        // Verify the discovery uses MdnsDiscoveryManager
+        final DiscoveryListener discListener = mock(DiscoveryListener.class);
+        client.discoverServices(SERVICE_TYPE, PROTOCOL, network, r -> r.run(), discListener);
+        waitForIdle();
+        verify(mDiscoveryManager).registerListener(anyString(), any(), any());
+
+        // Verify the discovery uses MdnsDiscoveryManager
+        final ResolveListener resolveListener = mock(ResolveListener.class);
+        client.resolveService(regInfo, r -> r.run(), resolveListener);
+        waitForIdle();
+        verify(mDiscoveryManager, times(2)).registerListener(anyString(), any(), any());
+    }
+
     private void waitForIdle() {
         HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
     }
 
     NsdService makeService() {
-        final NsdService service = new NsdService(mContext, mHandler, CLEANUP_DELAY_MS) {
+        final NsdService service = new NsdService(mContext, mHandler, CLEANUP_DELAY_MS, mDeps) {
             @Override
-            public INsdServiceConnector connect(INsdManagerCallback baseCb) {
+            public INsdServiceConnector connect(INsdManagerCallback baseCb,
+                    boolean runNewMdnsBackend) {
                 // Wrap the callback in a transparent mock, to mock asBinder returning a
                 // LinkToDeathRecorder. This will allow recording the binder death recipient
                 // registered on the callback. Use a transparent mock and not a spy as the actual
@@ -571,7 +1353,7 @@
                         AdditionalAnswers.delegatesTo(baseCb));
                 doReturn(new LinkToDeathRecorder()).when(cb).asBinder();
                 mCreatedCallbacks.add(cb);
-                return super.connect(cb);
+                return super.connect(cb, runNewMdnsBackend);
             }
         };
         return service;
@@ -599,6 +1381,19 @@
         verify(mMockMDnsM, timeout(cleanupDelayMs + TIMEOUT_MS)).stopDaemon();
     }
 
+    /**
+     * Return true if two service info are the same.
+     *
+     * Useful for argument matchers as {@link NsdServiceInfo} does not implement equals.
+     */
+    private boolean matches(NsdServiceInfo a, NsdServiceInfo b) {
+        return Objects.equals(a.getServiceName(), b.getServiceName())
+                && Objects.equals(a.getServiceType(), b.getServiceType())
+                && Objects.equals(a.getHost(), b.getHost())
+                && Objects.equals(a.getNetwork(), b.getNetwork())
+                && Objects.equals(a.getAttributes(), b.getAttributes());
+    }
+
     public static class TestHandler extends Handler {
         public Message lastMessage;
 
diff --git a/tests/unit/java/com/android/server/VpnManagerServiceTest.java b/tests/unit/java/com/android/server/VpnManagerServiceTest.java
index c8a93a6..deb56ef 100644
--- a/tests/unit/java/com/android/server/VpnManagerServiceTest.java
+++ b/tests/unit/java/com/android/server/VpnManagerServiceTest.java
@@ -131,6 +131,11 @@
                 Vpn vpn, VpnProfile profile) {
             return mLockdownVpnTracker;
         }
+
+        @Override
+        public @UserIdInt int getMainUserId() {
+            return UserHandle.USER_SYSTEM;
+        }
     }
 
     @Before
diff --git a/tests/unit/java/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilitiesTest.kt b/tests/unit/java/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilitiesTest.kt
new file mode 100644
index 0000000..f2d7aaa
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilitiesTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity
+
+import android.net.NetworkCapabilities
+import android.os.Build
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.SmallTest
+import com.android.frameworks.tests.net.R
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+class ApplicationSelfCertifiedNetworkCapabilitiesTest {
+    private val mResource = InstrumentationRegistry.getContext().getResources()
+    private val bandwidthCapability = NetworkCapabilities.Builder().apply {
+        addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+    }.build()
+    private val latencyCapability = NetworkCapabilities.Builder().apply {
+        addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+    }.build()
+    private val emptyCapability = NetworkCapabilities.Builder().build()
+    private val bothCapabilities = NetworkCapabilities.Builder().apply {
+        addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+        addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+    }.build()
+
+    @Test
+    fun parseXmlWithWrongTag_shouldIgnoreWrongTag() {
+        val parser = mResource.getXml(
+            R.xml.self_certified_capabilities_wrong_tag
+        )
+        val selfDeclaredCaps = ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser)
+        selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(latencyCapability)
+        selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(bandwidthCapability)
+    }
+
+    @Test
+    fun parseXmlWithWrongDeclaration_shouldThrowException() {
+        val parser = mResource.getXml(
+            R.xml.self_certified_capabilities_wrong_declaration
+        )
+        val exception = assertFailsWith<InvalidTagException> {
+            ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser)
+        }
+        assertThat(exception.message).contains("network-capabilities-declaration1")
+    }
+
+    @Test
+    fun checkIfSelfCertifiedNetworkCapabilitiesDeclared_shouldThrowExceptionWhenNoDeclaration() {
+        val parser = mResource.getXml(R.xml.self_certified_capabilities_other)
+        val selfDeclaredCaps = ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser)
+        val exception1 = assertFailsWith<SecurityException> {
+            selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(latencyCapability)
+        }
+        assertThat(exception1.message).contains(
+            ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_LATENCY
+        )
+        val exception2 = assertFailsWith<SecurityException> {
+            selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(bandwidthCapability)
+        }
+        assertThat(exception2.message).contains(
+            ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_BANDWIDTH
+        )
+    }
+
+    @Test
+    fun checkIfSelfCertifiedNetworkCapabilitiesDeclared_shouldPassIfDeclarationExist() {
+        val parser = mResource.getXml(R.xml.self_certified_capabilities_both)
+        val selfDeclaredCaps = ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser)
+        selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(latencyCapability)
+        selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(bandwidthCapability)
+        selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(bothCapabilities)
+        selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(emptyCapability)
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
new file mode 100644
index 0000000..9e0435d
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
@@ -0,0 +1,394 @@
+/*
+ * 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 com.android.server.connectivity;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.longThat;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.INetd;
+import android.net.ISocketKeepaliveCallback;
+import android.net.KeepalivePacketData;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.MarkMaskParcel;
+import android.net.NattKeepalivePacketData;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.connectivity.resources.R;
+import com.android.server.connectivity.KeepaliveTracker.KeepaliveInfo;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.HandlerUtils;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.FileDescriptor;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+public class AutomaticOnOffKeepaliveTrackerTest {
+    private static final String TAG = AutomaticOnOffKeepaliveTrackerTest.class.getSimpleName();
+    private static final int TEST_NETID = 0xA85;
+    private static final int TEST_NETID_FWMARK = 0x0A85;
+    private static final int OTHER_NETID = 0x1A85;
+    private static final int NETID_MASK = 0xffff;
+    private static final int TIMEOUT_MS = 30_000;
+    private static final int MOCK_RESOURCE_ID = 5;
+    private static final int TEST_KEEPALIVE_INTERVAL_SEC = 10;
+    private AutomaticOnOffKeepaliveTracker mAOOKeepaliveTracker;
+    private HandlerThread mHandlerThread;
+
+    @Mock INetd mNetd;
+    @Mock AutomaticOnOffKeepaliveTracker.Dependencies mDependencies;
+    @Mock Context mCtx;
+    @Mock AlarmManager mAlarmManager;
+    TestKeepaliveTracker mKeepaliveTracker;
+    AOOTestHandler mTestHandler;
+
+    // Hexadecimal representation of a SOCK_DIAG response with tcp info.
+    private static final String SOCK_DIAG_TCP_INET_HEX =
+            // struct nlmsghdr.
+            "14010000" +        // length = 276
+            "1400" +            // type = SOCK_DIAG_BY_FAMILY
+            "0301" +            // flags = NLM_F_REQUEST | NLM_F_DUMP
+            "00000000" +        // seqno
+            "00000000" +        // pid (0 == kernel)
+            // struct inet_diag_req_v2
+            "02" +              // family = AF_INET
+            "06" +              // state
+            "00" +              // timer
+            "00" +              // retrans
+            // inet_diag_sockid
+            "DEA5" +            // idiag_sport = 42462
+            "71B9" +            // idiag_dport = 47473
+            "0a006402000000000000000000000000" + // idiag_src = 10.0.100.2
+            "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8
+            "00000000" +            // idiag_if
+            "34ED000076270000" +    // idiag_cookie = 43387759684916
+            "00000000" +            // idiag_expires
+            "00000000" +            // idiag_rqueue
+            "00000000" +            // idiag_wqueue
+            "00000000" +            // idiag_uid
+            "00000000" +            // idiag_inode
+            // rtattr
+            "0500" +            // len = 5
+            "0800" +            // type = 8
+            "00000000" +        // data
+            "0800" +            // len = 8
+            "0F00" +            // type = 15(INET_DIAG_MARK)
+            "850A0C00" +        // data, socket mark=789125
+            "AC00" +            // len = 172
+            "0200" +            // type = 2(INET_DIAG_INFO)
+            // tcp_info
+            "01" +              // state = TCP_ESTABLISHED
+            "00" +              // ca_state = TCP_CA_OPEN
+            "05" +              // retransmits = 5
+            "00" +              // probes = 0
+            "00" +              // backoff = 0
+            "07" +              // option = TCPI_OPT_WSCALE|TCPI_OPT_SACK|TCPI_OPT_TIMESTAMPS
+            "88" +              // wscale = 8
+            "00" +              // delivery_rate_app_limited = 0
+            "4A911B00" +        // rto = 1806666
+            "00000000" +        // ato = 0
+            "2E050000" +        // sndMss = 1326
+            "18020000" +        // rcvMss = 536
+            "00000000" +        // unsacked = 0
+            "00000000" +        // acked = 0
+            "00000000" +        // lost = 0
+            "00000000" +        // retrans = 0
+            "00000000" +        // fackets = 0
+            "BB000000" +        // lastDataSent = 187
+            "00000000" +        // lastAckSent = 0
+            "BB000000" +        // lastDataRecv = 187
+            "BB000000" +        // lastDataAckRecv = 187
+            "DC050000" +        // pmtu = 1500
+            "30560100" +        // rcvSsthresh = 87600
+            "3E2C0900" +        // rttt = 601150
+            "1F960400" +        // rttvar = 300575
+            "78050000" +        // sndSsthresh = 1400
+            "0A000000" +        // sndCwnd = 10
+            "A8050000" +        // advmss = 1448
+            "03000000" +        // reordering = 3
+            "00000000" +        // rcvrtt = 0
+            "30560100" +        // rcvspace = 87600
+            "00000000" +        // totalRetrans = 0
+            "53AC000000000000" +    // pacingRate = 44115
+            "FFFFFFFFFFFFFFFF" +    // maxPacingRate = 18446744073709551615
+            "0100000000000000" +    // bytesAcked = 1
+            "0000000000000000" +    // bytesReceived = 0
+            "0A000000" +        // SegsOut = 10
+            "00000000" +        // SegsIn = 0
+            "00000000" +        // NotSentBytes = 0
+            "3E2C0900" +        // minRtt = 601150
+            "00000000" +        // DataSegsIn = 0
+            "00000000" +        // DataSegsOut = 0
+            "0000000000000000"; // deliverRate = 0
+    private static final String SOCK_DIAG_NO_TCP_INET_HEX =
+            // struct nlmsghdr
+            "14000000"     // length = 20
+            + "0300"         // type = NLMSG_DONE
+            + "0301"         // flags = NLM_F_REQUEST | NLM_F_DUMP
+            + "00000000"     // seqno
+            + "00000000"     // pid (0 == kernel)
+            // struct inet_diag_req_v2
+            + "02"           // family = AF_INET
+            + "06"           // state
+            + "00"           // timer
+            + "00";          // retrans
+    private static final byte[] SOCK_DIAG_NO_TCP_INET_BYTES =
+            HexEncoding.decode(SOCK_DIAG_NO_TCP_INET_HEX.toCharArray(), false);
+    private static final String TEST_RESPONSE_HEX =
+            SOCK_DIAG_TCP_INET_HEX + SOCK_DIAG_NO_TCP_INET_HEX;
+    private static final byte[] TEST_RESPONSE_BYTES =
+            HexEncoding.decode(TEST_RESPONSE_HEX.toCharArray(), false);
+
+    private class TestKeepaliveTracker extends KeepaliveTracker {
+        private KeepaliveInfo mKi;
+
+        TestKeepaliveTracker(@NonNull final Context context, @NonNull final Handler handler) {
+            super(context, handler);
+        }
+
+        public void setReturnedKeepaliveInfo(@NonNull final KeepaliveInfo ki) {
+            mKi = ki;
+        }
+
+        @NonNull
+        @Override
+        public KeepaliveInfo makeNattKeepaliveInfo(@Nullable final NetworkAgentInfo nai,
+                @Nullable final FileDescriptor fd, final int intervalSeconds,
+                @NonNull final ISocketKeepaliveCallback cb, @NonNull final String srcAddrString,
+                final int srcPort,
+                @NonNull final String dstAddrString, final int dstPort) {
+            if (null == mKi) {
+                throw new IllegalStateException("Must call setReturnedKeepaliveInfo");
+            }
+            return mKi;
+        }
+    }
+
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(PERMISSION_GRANTED).when(mCtx).checkPermission(any() /* permission */,
+                anyInt() /* pid */, anyInt() /* uid */);
+        ConnectivityResources.setResourcesContextForTest(mCtx);
+        final Resources mockResources = mock(Resources.class);
+        doReturn(new String[] { "0,3", "3,3" }).when(mockResources)
+                .getStringArray(R.array.config_networkSupportedKeepaliveCount);
+        doReturn(mockResources).when(mCtx).getResources();
+        doReturn(mNetd).when(mDependencies).getNetd();
+        doReturn(mAlarmManager).when(mDependencies).getAlarmManager(any());
+        doReturn(makeMarkMaskParcel(NETID_MASK, TEST_NETID_FWMARK)).when(mNetd)
+                .getFwmarkForNetwork(TEST_NETID);
+
+        doNothing().when(mDependencies).sendRequest(any(), any());
+
+        mHandlerThread = new HandlerThread("KeepaliveTrackerTest");
+        mHandlerThread.start();
+        mTestHandler = new AOOTestHandler(mHandlerThread.getLooper());
+        mKeepaliveTracker = new TestKeepaliveTracker(mCtx, mTestHandler);
+        doReturn(mKeepaliveTracker).when(mDependencies).newKeepaliveTracker(mCtx, mTestHandler);
+        doReturn(true).when(mDependencies).isFeatureEnabled(any(), anyBoolean());
+        mAOOKeepaliveTracker =
+                new AutomaticOnOffKeepaliveTracker(mCtx, mTestHandler, mDependencies);
+    }
+
+    private final class AOOTestHandler extends Handler {
+        public AutomaticOnOffKeepaliveTracker.AutomaticOnOffKeepalive mLastAutoKi = null;
+
+        AOOTestHandler(@NonNull final Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(@NonNull final Message msg) {
+            switch (msg.what) {
+                case AutomaticOnOffKeepaliveTracker.CMD_REQUEST_START_KEEPALIVE:
+                    Log.d(TAG, "Test handler received CMD_REQUEST_START_KEEPALIVE : " + msg);
+                    mAOOKeepaliveTracker.handleStartKeepalive(msg);
+                    break;
+                case AutomaticOnOffKeepaliveTracker.CMD_MONITOR_AUTOMATIC_KEEPALIVE:
+                    Log.d(TAG, "Test handler received CMD_MONITOR_AUTOMATIC_KEEPALIVE : " + msg);
+                    mLastAutoKi = mAOOKeepaliveTracker.getKeepaliveForBinder((IBinder) msg.obj);
+                    break;
+            }
+        }
+    }
+
+    @Test
+    public void testIsAnyTcpSocketConnected_runOnNonHandlerThread() throws Exception {
+        setupResponseWithSocketExisting();
+        assertThrows(IllegalStateException.class,
+                () -> mAOOKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID));
+    }
+
+    @Test
+    public void testIsAnyTcpSocketConnected_withTargetNetId() throws Exception {
+        setupResponseWithSocketExisting();
+        mTestHandler.post(
+                () -> assertTrue(mAOOKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID)));
+    }
+
+    @Test
+    public void testIsAnyTcpSocketConnected_withIncorrectNetId() throws Exception {
+        setupResponseWithSocketExisting();
+        mTestHandler.post(
+                () -> assertFalse(mAOOKeepaliveTracker.isAnyTcpSocketConnected(OTHER_NETID)));
+    }
+
+    @Test
+    public void testIsAnyTcpSocketConnected_noSocketExists() throws Exception {
+        setupResponseWithoutSocketExisting();
+        mTestHandler.post(
+                () -> assertFalse(mAOOKeepaliveTracker.isAnyTcpSocketConnected(TEST_NETID)));
+    }
+
+    @Test
+    public void testAlarm() throws Exception {
+        final InetAddress srcAddress = InetAddress.getByAddress(
+                new byte[] { (byte) 192, 0, 0, (byte) 129 });
+        final int srcPort = 12345;
+        final InetAddress dstAddress = InetAddress.getByAddress(new byte[] { 8, 8, 8, 8});
+        final int dstPort = 12345;
+
+        final NetworkAgentInfo nai = mock(NetworkAgentInfo.class);
+        nai.networkCapabilities = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_CELLULAR).build();
+        nai.networkInfo = new NetworkInfo(TYPE_MOBILE, 0 /* subtype */, "LTE", "LTE");
+        nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "test reason",
+                "test extra info");
+        nai.linkProperties = new LinkProperties();
+        nai.linkProperties.addLinkAddress(new LinkAddress(srcAddress, 24));
+
+        final Socket socket = new Socket();
+        socket.bind(null);
+        final FileDescriptor fd = socket.getFileDescriptor$();
+        final IBinder binder = new Binder();
+        final ISocketKeepaliveCallback cb = mock(ISocketKeepaliveCallback.class);
+        doReturn(binder).when(cb).asBinder();
+        final Network underpinnedNetwork = mock(Network.class);
+
+        final KeepalivePacketData kpd = new NattKeepalivePacketData(srcAddress, srcPort,
+                dstAddress, dstPort, new byte[] {1});
+        final KeepaliveInfo ki = mKeepaliveTracker.new KeepaliveInfo(cb, nai, kpd,
+                TEST_KEEPALIVE_INTERVAL_SEC, KeepaliveInfo.TYPE_NATT, fd);
+        mKeepaliveTracker.setReturnedKeepaliveInfo(ki);
+
+        // Mock elapsed real time to verify the alarm timer.
+        final long time = SystemClock.elapsedRealtime();
+        doReturn(time).when(mDependencies).getElapsedRealtime();
+
+        mAOOKeepaliveTracker.startNattKeepalive(nai, fd, 10 /* intervalSeconds */, cb,
+                srcAddress.toString(), srcPort, dstAddress.toString(), dstPort,
+                true /* automaticOnOffKeepalives */, underpinnedNetwork);
+        HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
+
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> listenerCaptor =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        // The alarm timer should be smaller than the keepalive delay. Verify the alarm trigger time
+        // is higher than base time but smaller than the keepalive delay.
+        verify(mAlarmManager).setExact(eq(AlarmManager.ELAPSED_REALTIME),
+                longThat(t -> t > time + 1000L && t < time + TEST_KEEPALIVE_INTERVAL_SEC * 1000L),
+                any() /* tag */, listenerCaptor.capture(), eq(mTestHandler));
+        final AlarmManager.OnAlarmListener listener = listenerCaptor.getValue();
+
+        // For realism, the listener should be posted on the handler
+        mTestHandler.post(() -> listener.onAlarm());
+        // Wait for the listener to be called. The listener enqueues a message to the handler.
+        HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
+        // Wait for the message posted by the listener to be processed.
+        HandlerUtils.waitForIdle(mTestHandler, TIMEOUT_MS);
+
+        assertNotNull(mTestHandler.mLastAutoKi);
+        assertEquals(cb, mTestHandler.mLastAutoKi.getCallback());
+        assertEquals(underpinnedNetwork, mTestHandler.mLastAutoKi.getUnderpinnedNetwork());
+        socket.close();
+    }
+
+    private void setupResponseWithSocketExisting() throws Exception {
+        final ByteBuffer tcpBufferV6 = getByteBuffer(TEST_RESPONSE_BYTES);
+        final ByteBuffer tcpBufferV4 = getByteBuffer(TEST_RESPONSE_BYTES);
+        doReturn(tcpBufferV6, tcpBufferV4).when(mDependencies).recvSockDiagResponse(any());
+    }
+
+    private void setupResponseWithoutSocketExisting() throws Exception {
+        final ByteBuffer tcpBufferV6 = getByteBuffer(SOCK_DIAG_NO_TCP_INET_BYTES);
+        final ByteBuffer tcpBufferV4 = getByteBuffer(SOCK_DIAG_NO_TCP_INET_BYTES);
+        doReturn(tcpBufferV6, tcpBufferV4).when(mDependencies).recvSockDiagResponse(any());
+    }
+
+    private MarkMaskParcel makeMarkMaskParcel(final int mask, final int mark) {
+        final MarkMaskParcel parcel = new MarkMaskParcel();
+        parcel.mask = mask;
+        parcel.mark = mark;
+        return parcel;
+    }
+
+    private ByteBuffer getByteBuffer(final byte[] bytes) {
+        final ByteBuffer buffer = ByteBuffer.wrap(bytes);
+        buffer.order(ByteOrder.nativeOrder());
+        return buffer;
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
index b651c33..4158663 100644
--- a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
@@ -313,8 +313,7 @@
          * Stop clatd.
          */
         @Override
-        public void stopClatd(@NonNull String iface, @NonNull String pfx96, @NonNull String v4,
-                @NonNull String v6, int pid) throws IOException {
+        public void stopClatd(int pid) throws IOException {
             if (pid == -1) {
                 fail("unsupported arg: " + pid);
             }
@@ -479,8 +478,7 @@
                 eq((short) PRIO_CLAT), eq((short) ETH_P_IP));
         inOrder.verify(mEgressMap).deleteEntry(eq(EGRESS_KEY));
         inOrder.verify(mIngressMap).deleteEntry(eq(INGRESS_KEY));
-        inOrder.verify(mDeps).stopClatd(eq(BASE_IFACE), eq(NAT64_PREFIX_STRING),
-                eq(XLAT_LOCAL_IPV4ADDR_STRING), eq(XLAT_LOCAL_IPV6ADDR_STRING), eq(CLATD_PID));
+        inOrder.verify(mDeps).stopClatd(eq(CLATD_PID));
         inOrder.verify(mCookieTagMap).deleteEntry(eq(COOKIE_TAG_KEY));
         assertNull(coordinator.getClatdTrackerForTesting());
         inOrder.verifyNoMoreInteractions();
diff --git a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 719314a..5881a8e 100644
--- a/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/unit/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -80,6 +80,7 @@
 
     private static final byte[] MAC_ADDR =
             {(byte)0x84, (byte)0xc9, (byte)0xb2, (byte)0x6a, (byte)0xed, (byte)0x4b};
+    private static final long NET_HANDLE = new Network(4291).getNetworkHandle();
 
     @Mock Context mCtx;
     @Mock IIpConnectivityMetrics mMockService;
@@ -607,7 +608,7 @@
 
     void wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp,
             String dstIp, int sport, int dport, long now) throws Exception {
-        String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface;
+        String prefix = NET_HANDLE + ":" + iface;
         mNetdListener.onWakeupEvent(prefix, uid, ether, ip, mac, srcIp, dstIp, sport, dport, now);
     }
 
diff --git a/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java b/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
new file mode 100644
index 0000000..d262255
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/KeepaliveStatsTrackerTest.java
@@ -0,0 +1,504 @@
+/*
+ * 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 com.android.server.connectivity;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.metrics.DailykeepaliveInfoReported;
+import com.android.metrics.DurationForNumOfKeepalive;
+import com.android.metrics.DurationPerNumOfKeepalive;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+public class KeepaliveStatsTrackerTest {
+    private static final int TEST_UID = 1234;
+
+    private KeepaliveStatsTracker mKeepaliveStatsTracker;
+    @Mock KeepaliveStatsTracker.Dependencies mDependencies;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        setUptimeMillis(0);
+        mKeepaliveStatsTracker = new KeepaliveStatsTracker(mDependencies);
+    }
+
+    private void setUptimeMillis(long time) {
+        doReturn(time).when(mDependencies).getUptimeMillis();
+    }
+
+    /**
+     * Asserts that a DurationPerNumOfKeepalive contains expected values
+     *
+     * @param expectRegisteredDurations integer array where the index is the number of concurrent
+     *     keepalives and the value is the expected duration of time that the tracker is in a state
+     *     with the given number of keepalives registered.
+     * @param expectActiveDurations integer array where the index is the number of concurrent
+     *     keepalives and the value is the expected duration of time that the tracker is in a state
+     *     with the given number of keepalives active.
+     * @param resultDurationsPerNumOfKeepalive the DurationPerNumOfKeepalive message to assert.
+     */
+    private void assertDurationMetrics(
+            int[] expectRegisteredDurations,
+            int[] expectActiveDurations,
+            DurationPerNumOfKeepalive resultDurationsPerNumOfKeepalive) {
+        final int maxNumOfKeepalive = expectRegisteredDurations.length;
+        assertEquals(maxNumOfKeepalive, expectActiveDurations.length);
+        assertEquals(
+                maxNumOfKeepalive,
+                resultDurationsPerNumOfKeepalive.getDurationForNumOfKeepaliveCount());
+        for (int numOfKeepalive = 0; numOfKeepalive < maxNumOfKeepalive; numOfKeepalive++) {
+            final DurationForNumOfKeepalive resultDurations =
+                    resultDurationsPerNumOfKeepalive.getDurationForNumOfKeepalive(numOfKeepalive);
+
+            assertEquals(numOfKeepalive, resultDurations.getNumOfKeepalive());
+            assertEquals(
+                    expectRegisteredDurations[numOfKeepalive],
+                    resultDurations.getKeepaliveRegisteredDurationsMsec());
+            assertEquals(
+                    expectActiveDurations[numOfKeepalive],
+                    resultDurations.getKeepaliveActiveDurationsMsec());
+        }
+    }
+
+    private void assertDailyKeepaliveInfoReported(
+            DailykeepaliveInfoReported dailyKeepaliveInfoReported,
+            int[] expectRegisteredDurations,
+            int[] expectActiveDurations) {
+        // TODO(b/273451360) Assert these values when they are filled.
+        assertFalse(dailyKeepaliveInfoReported.hasKeepaliveLifetimePerCarrier());
+        assertFalse(dailyKeepaliveInfoReported.hasKeepaliveRequests());
+        assertFalse(dailyKeepaliveInfoReported.hasAutomaticKeepaliveRequests());
+        assertFalse(dailyKeepaliveInfoReported.hasDistinctUserCount());
+        assertTrue(dailyKeepaliveInfoReported.getUidList().isEmpty());
+
+        final DurationPerNumOfKeepalive resultDurations =
+                dailyKeepaliveInfoReported.getDurationPerNumOfKeepalive();
+        assertDurationMetrics(expectRegisteredDurations, expectActiveDurations, resultDurations);
+    }
+
+    @Test
+    public void testNoKeepalive() {
+        final int writeTime = 5000;
+
+        setUptimeMillis(writeTime);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        // Expect that the durations are all in numOfKeepalive = 0.
+        final int[] expectRegisteredDurations = new int[] {writeTime};
+        final int[] expectActiveDurations = new int[] {writeTime};
+
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                expectRegisteredDurations,
+                expectActiveDurations);
+    }
+
+    /*
+     * Diagram of test (not to scale):
+     * Key: S - Start/Stop, P - Pause, R - Resume, W - Write
+     *
+     * Keepalive     S                          W
+     * Timeline  |------------------------------|
+     */
+    @Test
+    public void testOneKeepalive_startOnly() {
+        final int startTime = 1000;
+        final int writeTime = 5000;
+
+        setUptimeMillis(startTime);
+        mKeepaliveStatsTracker.onStartKeepalive();
+
+        setUptimeMillis(writeTime);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        // The keepalive is never stopped, expect the duration for numberOfKeepalive of 1 to range
+        // from startTime to writeTime.
+        final int[] expectRegisteredDurations = new int[] {startTime, writeTime - startTime};
+        final int[] expectActiveDurations = new int[] {startTime, writeTime - startTime};
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                expectRegisteredDurations,
+                expectActiveDurations);
+    }
+
+    /*
+     * Diagram of test (not to scale):
+     * Key: S - Start/Stop, P - Pause, R - Resume, W - Write
+     *
+     * Keepalive     S       P                  W
+     * Timeline  |------------------------------|
+     */
+    @Test
+    public void testOneKeepalive_paused() {
+        final int startTime = 1000;
+        final int pauseTime = 2030;
+        final int writeTime = 5000;
+
+        setUptimeMillis(startTime);
+        mKeepaliveStatsTracker.onStartKeepalive();
+
+        setUptimeMillis(pauseTime);
+        mKeepaliveStatsTracker.onPauseKeepalive();
+
+        setUptimeMillis(writeTime);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        // The keepalive is paused but not stopped, expect the registered duration for
+        // numberOfKeepalive of 1 to still range from startTime to writeTime while the active
+        // duration stops at pauseTime.
+        final int[] expectRegisteredDurations = new int[] {startTime, writeTime - startTime};
+        final int[] expectActiveDurations =
+                new int[] {startTime + (writeTime - pauseTime), pauseTime - startTime};
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                expectRegisteredDurations,
+                expectActiveDurations);
+    }
+
+    /*
+     * Diagram of test (not to scale):
+     * Key: S - Start/Stop, P - Pause, R - Resume, W - Write
+     *
+     * Keepalive     S       P        R         W
+     * Timeline  |------------------------------|
+     */
+    @Test
+    public void testOneKeepalive_resumed() {
+        final int startTime = 1000;
+        final int pauseTime = 2030;
+        final int resumeTime = 3450;
+        final int writeTime = 5000;
+
+        setUptimeMillis(startTime);
+        mKeepaliveStatsTracker.onStartKeepalive();
+
+        setUptimeMillis(pauseTime);
+        mKeepaliveStatsTracker.onPauseKeepalive();
+
+        setUptimeMillis(resumeTime);
+        mKeepaliveStatsTracker.onResumeKeepalive();
+
+        setUptimeMillis(writeTime);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        // The keepalive is paused and resumed but not stopped, expect the registered duration for
+        // numberOfKeepalive of 1 to still range from startTime to writeTime while the active
+        // duration stops at pauseTime but resumes at resumeTime and stops at writeTime.
+        final int[] expectRegisteredDurations = new int[] {startTime, writeTime - startTime};
+        final int[] expectActiveDurations =
+                new int[] {
+                    startTime + (resumeTime - pauseTime),
+                    (pauseTime - startTime) + (writeTime - resumeTime)
+                };
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                expectRegisteredDurations,
+                expectActiveDurations);
+    }
+
+    /*
+     * Diagram of test (not to scale):
+     * Key: S - Start/Stop, P - Pause, R - Resume, W - Write
+     *
+     * Keepalive     S       P      R     S     W
+     * Timeline  |------------------------------|
+     */
+    @Test
+    public void testOneKeepalive_stopped() {
+        final int startTime = 1000;
+        final int pauseTime = 2930;
+        final int resumeTime = 3452;
+        final int stopTime = 4157;
+        final int writeTime = 5000;
+
+        setUptimeMillis(startTime);
+        mKeepaliveStatsTracker.onStartKeepalive();
+
+        setUptimeMillis(pauseTime);
+        mKeepaliveStatsTracker.onPauseKeepalive();
+
+        setUptimeMillis(resumeTime);
+        mKeepaliveStatsTracker.onResumeKeepalive();
+
+        setUptimeMillis(stopTime);
+        mKeepaliveStatsTracker.onStopKeepalive(/* wasActive= */ true);
+
+        setUptimeMillis(writeTime);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        // The keepalive is now stopped, expect the registered duration for numberOfKeepalive of 1
+        // to now range from startTime to stopTime while the active duration stops at pauseTime but
+        // resumes at resumeTime and stops again at stopTime.
+        final int[] expectRegisteredDurations =
+                new int[] {startTime + (writeTime - stopTime), stopTime - startTime};
+        final int[] expectActiveDurations =
+                new int[] {
+                    startTime + (resumeTime - pauseTime) + (writeTime - stopTime),
+                    (pauseTime - startTime) + (stopTime - resumeTime)
+                };
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                expectRegisteredDurations,
+                expectActiveDurations);
+    }
+
+    /*
+     * Diagram of test (not to scale):
+     * Key: S - Start/Stop, P - Pause, R - Resume, W - Write
+     *
+     * Keepalive     S       P            S     W
+     * Timeline  |------------------------------|
+     */
+    @Test
+    public void testOneKeepalive_pausedStopped() {
+        final int startTime = 1000;
+        final int pauseTime = 2930;
+        final int stopTime = 4157;
+        final int writeTime = 5000;
+
+        setUptimeMillis(startTime);
+        mKeepaliveStatsTracker.onStartKeepalive();
+
+        setUptimeMillis(pauseTime);
+        mKeepaliveStatsTracker.onPauseKeepalive();
+
+        setUptimeMillis(stopTime);
+        mKeepaliveStatsTracker.onStopKeepalive(/* wasActive= */ false);
+
+        setUptimeMillis(writeTime);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        // The keepalive is stopped while paused, expect the registered duration for
+        // numberOfKeepalive of 1 to range from startTime to stopTime while the active duration
+        // simply stops at pauseTime.
+        final int[] expectRegisteredDurations =
+                new int[] {startTime + (writeTime - stopTime), stopTime - startTime};
+        final int[] expectActiveDurations =
+                new int[] {startTime + (writeTime - pauseTime), (pauseTime - startTime)};
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                expectRegisteredDurations,
+                expectActiveDurations);
+    }
+
+    /*
+     * Diagram of test (not to scale):
+     * Key: S - Start/Stop, P - Pause, R - Resume, W - Write
+     *
+     * Keepalive     S  P R P R P R       S     W
+     * Timeline  |------------------------------|
+     */
+    @Test
+    public void testOneKeepalive_multiplePauses() {
+        final int startTime = 1000;
+        // Alternating timestamps of pause and resume
+        final int[] pauseResumeTimes = new int[] {1200, 1400, 1700, 2000, 2400, 2800};
+        final int stopTime = 4000;
+        final int writeTime = 5000;
+
+        setUptimeMillis(startTime);
+        mKeepaliveStatsTracker.onStartKeepalive();
+
+        for (int i = 0; i < pauseResumeTimes.length; i++) {
+            setUptimeMillis(pauseResumeTimes[i]);
+            if (i % 2 == 0) {
+                mKeepaliveStatsTracker.onPauseKeepalive();
+            } else {
+                mKeepaliveStatsTracker.onResumeKeepalive();
+            }
+        }
+
+        setUptimeMillis(stopTime);
+        mKeepaliveStatsTracker.onStopKeepalive(/* wasActive= */ true);
+
+        setUptimeMillis(writeTime);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        final int[] expectRegisteredDurations =
+                new int[] {startTime + (writeTime - stopTime), stopTime - startTime};
+        final int[] expectActiveDurations =
+                new int[] {
+                    startTime + /* sum of (Resume - Pause) */ (900) + (writeTime - stopTime),
+                    (pauseResumeTimes[0] - startTime)
+                            + /* sum of (Pause - Resume) */ (700)
+                            + (stopTime - pauseResumeTimes[5])
+                };
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                expectRegisteredDurations,
+                expectActiveDurations);
+    }
+
+    /*
+     * Diagram of test (not to scale):
+     * Key: S - Start/Stop, P - Pause, R - Resume, W - Write
+     *
+     * Keepalive1    S1  P1     R1         S1    W
+     * Keepalive2           S2     P2   R2       W
+     * Timeline   |------------------------------|
+     */
+    @Test
+    public void testTwoKeepalives() {
+        // The suffix 1/2 indicates which keepalive it is referring to.
+        final int startTime1 = 1000;
+        final int pauseTime1 = 1500;
+        final int startTime2 = 2000;
+        final int resumeTime1 = 2500;
+        final int pauseTime2 = 3000;
+        final int resumeTime2 = 3500;
+        final int stopTime1 = 4157;
+        final int writeTime = 5000;
+
+        setUptimeMillis(startTime1);
+        mKeepaliveStatsTracker.onStartKeepalive();
+
+        setUptimeMillis(pauseTime1);
+        mKeepaliveStatsTracker.onPauseKeepalive();
+
+        setUptimeMillis(startTime2);
+        mKeepaliveStatsTracker.onStartKeepalive();
+
+        setUptimeMillis(resumeTime1);
+        mKeepaliveStatsTracker.onResumeKeepalive();
+
+        setUptimeMillis(pauseTime2);
+        mKeepaliveStatsTracker.onPauseKeepalive();
+
+        setUptimeMillis(resumeTime2);
+        mKeepaliveStatsTracker.onResumeKeepalive();
+
+        setUptimeMillis(stopTime1);
+        mKeepaliveStatsTracker.onStopKeepalive(/* wasActive= */ true);
+
+        setUptimeMillis(writeTime);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        // With two keepalives, the number of concurrent keepalives can vary from 0-2 depending on
+        // both keepalive states.
+        final int[] expectRegisteredDurations =
+                new int[] {
+                    startTime1,
+                    // 1 registered keepalive before keepalive2 starts and after keepalive1 stops.
+                    (startTime2 - startTime1) + (writeTime - stopTime1),
+                    // 2 registered keepalives between keepalive2 start and keepalive1 stop.
+                    stopTime1 - startTime2
+                };
+
+        final int[] expectActiveDurations =
+                new int[] {
+                    // 0 active keepalives when keepalive1 is paused before keepalive2 starts.
+                    startTime1 + (startTime2 - pauseTime1),
+                    // 1 active keepalive before keepalive1 is paused.
+                    (pauseTime1 - startTime1)
+                            // before keepalive1 is resumed and after keepalive2 starts.
+                            + (resumeTime1 - startTime2)
+                            // during keepalive2 is paused since keepalive1 has been resumed.
+                            + (resumeTime2 - pauseTime2)
+                            // after keepalive1 stops since keepalive2 has been resumed.
+                            + (writeTime - stopTime1),
+                    // 2 active keepalives before keepalive2 is paused and before keepalive1 stops.
+                    (pauseTime2 - resumeTime1) + (stopTime1 - resumeTime2)
+                };
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                expectRegisteredDurations,
+                expectActiveDurations);
+    }
+
+    /*
+     * Diagram of test (not to scale):
+     * Key: S - Start/Stop, P - Pause, R - Resume, W - Write
+     *
+     * Keepalive     S   W(reset+W)         S    W
+     * Timeline   |------------------------------|
+     */
+    @Test
+    public void testResetMetrics() {
+        final int startTime = 1000;
+        final int writeTime = 5000;
+        final int stopTime = 7000;
+        final int writeTime2 = 10000;
+
+        setUptimeMillis(startTime);
+        mKeepaliveStatsTracker.onStartKeepalive();
+
+        setUptimeMillis(writeTime);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        // Same expect as testOneKeepalive_startOnly
+        final int[] expectRegisteredDurations = new int[] {startTime, writeTime - startTime};
+        final int[] expectActiveDurations = new int[] {startTime, writeTime - startTime};
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported,
+                expectRegisteredDurations,
+                expectActiveDurations);
+
+        // Reset metrics
+        mKeepaliveStatsTracker.resetMetrics();
+
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported2 =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+        // Expect the stored durations to be 0 but still contain the number of keepalive = 1.
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported2,
+                /* expectRegisteredDurations= */ new int[] {0, 0},
+                /* expectActiveDurations= */ new int[] {0, 0});
+
+        // Expect that the keepalive is still registered after resetting so it can be stopped.
+        setUptimeMillis(stopTime);
+        mKeepaliveStatsTracker.onStopKeepalive(/* wasActive= */ true);
+
+        setUptimeMillis(writeTime2);
+        final DailykeepaliveInfoReported dailyKeepaliveInfoReported3 =
+                mKeepaliveStatsTracker.buildKeepaliveMetrics();
+
+        final int[] expectRegisteredDurations2 =
+                new int[] {writeTime2 - stopTime, stopTime - writeTime};
+        final int[] expectActiveDurations2 =
+                new int[] {writeTime2 - stopTime, stopTime - writeTime};
+        assertDailyKeepaliveInfoReported(
+                dailyKeepaliveInfoReported3,
+                expectRegisteredDurations2,
+                expectActiveDurations2);
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java
index 0d371fa..e6c0c83 100644
--- a/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -34,7 +34,6 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
-import android.net.ConnectivityResources;
 import android.net.IDnsResolver;
 import android.net.INetd;
 import android.net.LinkProperties;
diff --git a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt
index b52e8a8..f19ba4f 100644
--- a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt
@@ -21,10 +21,8 @@
 import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER
 import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE
 import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY
-import android.net.ConnectivityResources
 import android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI
 import android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE
-import com.android.server.connectivity.MultinetworkPolicyTracker.ActiveDataSubscriptionIdListener
 import android.os.Build
 import android.os.Handler
 import android.os.test.TestLooper
@@ -37,6 +35,7 @@
 import com.android.connectivity.resources.R
 import com.android.internal.util.test.FakeSettingsProvider
 import com.android.modules.utils.build.SdkLevel
+import com.android.server.connectivity.MultinetworkPolicyTracker.ActiveDataSubscriptionIdListener
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
 import org.junit.After
diff --git a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt
index 744c020..4c82c76 100644
--- a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt
+++ b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt
@@ -1,7 +1,6 @@
 package com.android.server.connectivity
 
 import android.content.res.Resources
-import android.net.ConnectivityResources
 import android.provider.DeviceConfig
 import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
 import android.provider.DeviceConfig.OnPropertiesChangedListener
diff --git a/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 7d6c3ae..d667662 100644
--- a/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
@@ -41,6 +42,8 @@
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
+import libcore.util.EmptyArray;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -60,6 +63,9 @@
     private static final String EXAMPLE_IPV4 = "192.0.2.1";
     private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
 
+    private static final Network TEST_WIFI_NETWORK = new Network(5391);
+    private static final Network TEST_CELL_NETWORK = new Network(5832);
+
     private static final byte[] MAC_ADDR =
             {(byte)0x84, (byte)0xc9, (byte)0xb2, (byte)0x6a, (byte)0xed, (byte)0x4b};
 
@@ -76,6 +82,8 @@
     public void setUp() {
         mCm = mock(ConnectivityManager.class);
         mService = new NetdEventListenerService(mCm);
+        doReturn(CAPABILITIES_WIFI).when(mCm).getNetworkCapabilities(TEST_WIFI_NETWORK);
+        doReturn(CAPABILITIES_CELL).when(mCm).getNetworkCapabilities(TEST_CELL_NETWORK);
     }
 
     @Test
@@ -109,19 +117,25 @@
         wakeupEvent(iface, uids[5], v4, tcp, mac, srcIp, dstIp, sport, dport, now);
         wakeupEvent(iface, uids[6], v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
         wakeupEvent(iface, uids[7], v6, tcp, mac, srcIp6, dstIp6, sport, dport, now);
-        wakeupEvent(iface, uids[8], v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
+        wakeupEvent("rmnet0", uids[8], v6, udp, EmptyArray.BYTE, srcIp6, dstIp6, sport, dport, now,
+                TEST_CELL_NETWORK);
 
         String[] events2 = remove(listNetdEvent(), baseline);
-        int expectedLength2 = uids.length + 1; // +1 for the WakeupStats line
+        int expectedLength2 = uids.length + 2; // +2 for the WakeupStats headers
         assertEquals(expectedLength2, events2.length);
+
         assertStringContains(events2[0], "WakeupStats");
-        assertStringContains(events2[0], "wlan0");
-        assertStringContains(events2[0], "0x800");
+        assertStringContains(events2[0], "rmnet0");
         assertStringContains(events2[0], "0x86dd");
+
+        assertStringContains(events2[1], "WakeupStats");
+        assertStringContains(events2[1], "wlan0");
+        assertStringContains(events2[1], "0x800");
+        assertStringContains(events2[1], "0x86dd");
         for (int i = 0; i < uids.length; i++) {
-            String got = events2[i+1];
+            String got = events2[i + 2];
             assertStringContains(got, "WakeupEvent");
-            assertStringContains(got, "wlan0");
+            assertStringContains(got, ((i == 8) ? "rmnet0" : "wlan0"));
             assertStringContains(got, "uid: " + uids[i]);
         }
 
@@ -132,11 +146,13 @@
         }
 
         String[] events3 = remove(listNetdEvent(), baseline);
-        int expectedLength3 = BUFFER_LENGTH + 1; // +1 for the WakeupStats line
+        int expectedLength3 = BUFFER_LENGTH + 2; // +2 for the WakeupStats headers
         assertEquals(expectedLength3, events3.length);
-        assertStringContains(events2[0], "WakeupStats");
-        assertStringContains(events2[0], "wlan0");
-        for (int i = 1; i < expectedLength3; i++) {
+        assertStringContains(events3[0], "WakeupStats");
+        assertStringContains(events3[0], "rmnet0");
+        assertStringContains(events3[1], "WakeupStats");
+        assertStringContains(events3[1], "wlan0");
+        for (int i = 2; i < expectedLength3; i++) {
             String got = events3[i];
             assertStringContains(got, "WakeupEvent");
             assertStringContains(got, "wlan0");
@@ -171,19 +187,24 @@
         final int icmp6 = 58;
 
         wakeupEvent("wlan0", 1000, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
-        wakeupEvent("rmnet0", 10123, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
+        wakeupEvent("rmnet0", 10123, v4, tcp, mac, srcIp, dstIp, sport, dport, now,
+                TEST_CELL_NETWORK);
         wakeupEvent("wlan0", 1000, v4, udp, mac, srcIp, dstIp, sport, dport, now);
-        wakeupEvent("rmnet0", 10008, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
+        wakeupEvent("rmnet0", 10008, v4, tcp, EmptyArray.BYTE, srcIp, dstIp, sport, dport, now,
+                TEST_CELL_NETWORK);
         wakeupEvent("wlan0", -1, v6, icmp6, mac, srcIp6, dstIp6, sport, dport, now);
         wakeupEvent("wlan0", 10008, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
-        wakeupEvent("rmnet0", 1000, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
+        wakeupEvent("rmnet0", 1000, v4, tcp, mac, srcIp, dstIp, sport, dport, now,
+                TEST_CELL_NETWORK);
         wakeupEvent("wlan0", 10004, v4, udp, mac, srcIp, dstIp, sport, dport, now);
         wakeupEvent("wlan0", 1000, v6, tcp, mac, srcIp6, dstIp6, sport, dport, now);
         wakeupEvent("wlan0", 0, v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
         wakeupEvent("wlan0", -1, v6, icmp6, mac, srcIp6, dstIp6, sport, dport, now);
-        wakeupEvent("rmnet0", 10052, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
+        wakeupEvent("rmnet0", 10052, v4, tcp, mac, srcIp, dstIp, sport, dport, now,
+                TEST_CELL_NETWORK);
         wakeupEvent("wlan0", 0, v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
-        wakeupEvent("rmnet0", 1000, v6, tcp, mac, srcIp6, dstIp6, sport, dport, now);
+        wakeupEvent("rmnet0", 1000, v6, tcp, null, srcIp6, dstIp6, sport, dport, now,
+                TEST_CELL_NETWORK);
         wakeupEvent("wlan0", 1010, v4, udp, mac, srcIp, dstIp, sport, dport, now);
 
         String got = flushStatistics();
@@ -212,7 +233,7 @@
                 "    >",
                 "    l2_broadcast_count: 0",
                 "    l2_multicast_count: 0",
-                "    l2_unicast_count: 5",
+                "    l2_unicast_count: 3",
                 "    no_uid_wakeups: 0",
                 "    non_application_wakeups: 0",
                 "    root_wakeups: 0",
@@ -497,8 +518,13 @@
     }
 
     void wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp,
-            String dstIp, int sport, int dport, long now) throws Exception {
-        String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface;
+            String dstIp, int sport, int dport, long now) {
+        wakeupEvent(iface, uid, ether, ip, mac, srcIp, dstIp, sport, dport, now, TEST_WIFI_NETWORK);
+    }
+
+    void wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp,
+            String dstIp, int sport, int dport, long now, Network network) {
+        String prefix = network.getNetworkHandle() + ":" + iface;
         mService.onWakeupEvent(prefix, uid, ether, ip, mac, srcIp, dstIp, sport, dport, now);
     }
 
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 9a5298d..a27a0bf 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -51,13 +51,14 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
-import android.net.ConnectivityResources;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.PowerManager;
 import android.os.UserHandle;
 import android.telephony.TelephonyManager;
+import android.testing.PollingCheck;
 import android.util.DisplayMetrics;
 import android.widget.TextView;
 
@@ -391,7 +392,15 @@
 
         final Instrumentation instr = InstrumentationRegistry.getInstrumentation();
         final UiDevice uiDevice =  UiDevice.getInstance(instr);
-        UiDevice.getInstance(instr).pressHome();
+        final Context ctx = instr.getContext();
+        final PowerManager pm = ctx.getSystemService(PowerManager.class);
+
+        // Wake up the device (it has no effect if the device is already awake).
+        uiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        uiDevice.executeShellCommand("wm dismiss-keyguard");
+        PollingCheck.check("Wait for the screen to be turned on failed, timeout=" + TEST_TIMEOUT_MS,
+                TEST_TIMEOUT_MS, () -> pm.isInteractive());
+        uiDevice.pressHome();
 
         // UiDevice.getLauncherPackageName() requires the test manifest to have a <queries> tag for
         // the launcher intent.
@@ -404,7 +413,6 @@
         // Non-"no internet" notifications are not affected
         verify(mNotificationManager).notify(eq(TEST_NOTIF_TAG), eq(NETWORK_SWITCH.eventId), any());
 
-        final Context ctx = instr.getContext();
         final String testAction = "com.android.connectivity.coverage.TEST_DIALOG";
         final Intent intent = new Intent(testAction)
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index 8076edb..cf02e3a 100644
--- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -46,7 +46,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.AdditionalMatchers.aryEq;
 import static org.mockito.ArgumentMatchers.any;
@@ -844,7 +843,6 @@
         // When VPN is disconnected, expect rules to be torn down
         mPermissionMonitor.onVpnUidRangesRemoved(ifName, vpnRange2, VPN_UID);
         verify(mBpfNetMaps).removeUidInterfaceRules(aryEq(new int[] {MOCK_UID12}));
-        assertNull(mPermissionMonitor.getVpnInterfaceUidRanges(ifName));
     }
 
     @Test
@@ -915,7 +913,6 @@
         verify(mBpfNetMaps, times(2)).updateUidLockdownRule(anyInt(), eq(true) /* add */);
         verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID11, true /* add */);
         verify(mBpfNetMaps).updateUidLockdownRule(VPN_UID, true /* add */);
-        assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(lockdownRange));
 
         reset(mBpfNetMaps);
 
@@ -924,7 +921,6 @@
         verify(mBpfNetMaps, times(2)).updateUidLockdownRule(anyInt(), eq(false) /* add */);
         verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID11, false /* add */);
         verify(mBpfNetMaps).updateUidLockdownRule(VPN_UID, false /* add */);
-        assertTrue(mPermissionMonitor.getVpnLockdownUidRanges().isEmpty());
     }
 
     @Test
@@ -944,7 +940,6 @@
         mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, lockdownRange);
         verify(mBpfNetMaps).updateUidLockdownRule(anyInt(), eq(true) /* add */);
         verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID11, true /* add */);
-        assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(lockdownRange));
 
         reset(mBpfNetMaps);
 
@@ -952,7 +947,6 @@
         // already has the rule
         mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, lockdownRange);
         verify(mBpfNetMaps, never()).updateUidLockdownRule(anyInt(),  anyBoolean());
-        assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(lockdownRange));
 
         reset(mBpfNetMaps);
 
@@ -960,7 +954,6 @@
         // the range 2 times.
         mPermissionMonitor.updateVpnLockdownUidRanges(false /* add */, lockdownRange);
         verify(mBpfNetMaps, never()).updateUidLockdownRule(anyInt(),  anyBoolean());
-        assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(lockdownRange));
 
         reset(mBpfNetMaps);
 
@@ -969,7 +962,6 @@
         mPermissionMonitor.updateVpnLockdownUidRanges(false /* add */, lockdownRange);
         verify(mBpfNetMaps).updateUidLockdownRule(anyInt(), eq(false) /* add */);
         verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID11, false /* add */);
-        assertTrue(mPermissionMonitor.getVpnLockdownUidRanges().isEmpty());
     }
 
     @Test
@@ -990,7 +982,6 @@
         mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, lockdownRangeDuplicates);
         verify(mBpfNetMaps).updateUidLockdownRule(anyInt(), eq(true) /* add */);
         verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID11, true /* add */);
-        assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(lockdownRange));
 
         reset(mBpfNetMaps);
 
@@ -998,7 +989,6 @@
         // ranges we added contains duplicated uid ranges.
         mPermissionMonitor.updateVpnLockdownUidRanges(false /* add */, lockdownRange);
         verify(mBpfNetMaps, never()).updateUidLockdownRule(anyInt(), anyBoolean());
-        assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(lockdownRange));
 
         reset(mBpfNetMaps);
 
@@ -1006,7 +996,6 @@
         mPermissionMonitor.updateVpnLockdownUidRanges(false /* add */, lockdownRange);
         verify(mBpfNetMaps).updateUidLockdownRule(anyInt(), eq(false) /* add */);
         verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID11, false /* add */);
-        assertTrue(mPermissionMonitor.getVpnLockdownUidRanges().isEmpty());
     }
 
     @Test
diff --git a/tests/unit/java/com/android/server/connectivity/UidRangeUtilsTest.java b/tests/unit/java/com/android/server/connectivity/UidRangeUtilsTest.java
index b8c552e..ad4785d 100644
--- a/tests/unit/java/com/android/server/connectivity/UidRangeUtilsTest.java
+++ b/tests/unit/java/com/android/server/connectivity/UidRangeUtilsTest.java
@@ -402,4 +402,27 @@
         expected.add(uids20_24);
         assertEquals(expected, UidRangeUtils.convertArrayToUidRange(input));
     }
+
+    @Test
+    public void testSortRangesByStartUid() throws Exception {
+        final UidRange uid1 = new UidRange(100, 110);
+        final UidRange uid2 = new UidRange(120, 130);
+        final UidRange[] unsortedRanges = new UidRange[] {uid2, uid1};
+        final UidRange[] sortedRanges = UidRangeUtils.sortRangesByStartUid(unsortedRanges);
+        assertEquals(uid1, sortedRanges[0]);
+        assertEquals(uid2, sortedRanges[1]);
+    }
+
+    @Test
+    public void testSortedRangesContainOverlap() throws Exception {
+        final UidRange uid1 = new UidRange(100, 110);
+        final UidRange uid2 = new UidRange(109, 120);
+        final UidRange uid3 = new UidRange(120, 130);
+        final UidRange[] overlapRanges1 = new UidRange[] {uid1, uid2};
+        final UidRange[] overlapRanges2 = new UidRange[] {uid2, uid3};
+        final UidRange[] notOverlapRanges = new UidRange[] {uid1, uid3};
+        assertTrue(UidRangeUtils.sortedRangesContainOverlap(overlapRanges1));
+        assertTrue(UidRangeUtils.sortedRangesContainOverlap(overlapRanges2));
+        assertFalse(UidRangeUtils.sortedRangesContainOverlap(notOverlapRanges));
+    }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 677e7b6..2d2819c 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -25,15 +25,38 @@
 import static android.net.ConnectivityManager.NetworkCallback;
 import static android.net.INetd.IF_STATE_DOWN;
 import static android.net.INetd.IF_STATE_UP;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 import static android.net.VpnManager.TYPE_VPN_PLATFORM;
+import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
+import static android.net.cts.util.IkeSessionTestUtils.TEST_IDENTITY;
+import static android.net.cts.util.IkeSessionTestUtils.TEST_KEEPALIVE_TIMEOUT_UNSET;
+import static android.net.cts.util.IkeSessionTestUtils.getTestIkeSessionParams;
 import static android.net.ipsec.ike.IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE;
-import static android.os.Build.VERSION_CODES.S_V2;
+import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_AUTO;
+import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_NONE;
+import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_UDP;
+import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_AUTO;
+import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_IPV4;
+import static android.net.ipsec.ike.IkeSessionParams.ESP_IP_VERSION_IPV6;
 import static android.os.UserHandle.PER_USER_RANGE;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL;
+import static android.telephony.CarrierConfigManager.KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT;
+import static android.telephony.CarrierConfigManager.KEY_PREFERRED_IKE_PROTOCOL_INT;
 
 import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
+import static com.android.server.connectivity.Vpn.AUTOMATIC_KEEPALIVE_DELAY_SECONDS;
+import static com.android.server.connectivity.Vpn.DEFAULT_LONG_LIVED_TCP_CONNS_EXPENSIVE_TIMEOUT_SEC;
+import static com.android.server.connectivity.Vpn.DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT;
+import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_AUTO;
+import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV4_UDP;
+import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV6_ESP;
+import static com.android.server.connectivity.Vpn.PREFERRED_IKE_PROTOCOL_IPV6_UDP;
 import static com.android.testutils.Cleanup.testAndCleanup;
-import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import static com.android.testutils.MiscAsserts.assertThrows;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -50,6 +73,7 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.longThat;
 import static org.mockito.Mockito.after;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
@@ -99,6 +123,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo.DetailedState;
 import android.net.RouteInfo;
+import android.net.TelephonyNetworkSpecifier;
 import android.net.UidRangeParcel;
 import android.net.VpnManager;
 import android.net.VpnProfileState;
@@ -106,15 +131,20 @@
 import android.net.VpnTransportInfo;
 import android.net.ipsec.ike.ChildSessionCallback;
 import android.net.ipsec.ike.ChildSessionConfiguration;
+import android.net.ipsec.ike.IkeFqdnIdentification;
 import android.net.ipsec.ike.IkeSessionCallback;
 import android.net.ipsec.ike.IkeSessionConfiguration;
 import android.net.ipsec.ike.IkeSessionConnectionInfo;
+import android.net.ipsec.ike.IkeSessionParams;
 import android.net.ipsec.ike.IkeTrafficSelector;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
 import android.net.ipsec.ike.exceptions.IkeException;
 import android.net.ipsec.ike.exceptions.IkeNetworkLostException;
 import android.net.ipsec.ike.exceptions.IkeNonProtocolException;
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
 import android.net.ipsec.ike.exceptions.IkeTimeoutException;
+import android.net.vcn.VcnTransportInfo;
+import android.net.wifi.WifiInfo;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
 import android.os.ConditionVariable;
@@ -128,11 +158,16 @@
 import android.os.test.TestLooper;
 import android.provider.Settings;
 import android.security.Credentials;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Pair;
 import android.util.Range;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
@@ -140,14 +175,13 @@
 import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnProfile;
 import com.android.internal.util.HexDump;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.IpSecService;
 import com.android.server.VpnTestBase;
 import com.android.server.vcn.util.PersistableBundleUtils;
 import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -164,6 +198,7 @@
 import java.io.FileDescriptor;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.io.StringWriter;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
@@ -181,7 +216,8 @@
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
-import java.util.stream.Stream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Tests for {@link Vpn}.
@@ -189,9 +225,8 @@
  * Build, install and run with:
  *  runtest frameworks-net -c com.android.server.connectivity.VpnTest
  */
-@RunWith(DevSdkIgnoreRunner.class)
+@RunWith(AndroidJUnit4.class)
 @SmallTest
-@IgnoreUpTo(S_V2)
 public class VpnTest extends VpnTestBase {
     private static final String TAG = "VpnTest";
 
@@ -248,10 +283,14 @@
     private static final String TEST_IFACE_NAME = "TEST_IFACE";
     private static final int TEST_TUNNEL_RESOURCE_ID = 0x2345;
     private static final long TEST_TIMEOUT_MS = 500L;
+    private static final long TIMEOUT_CROSSTHREAD_MS = 20_000L;
     private static final String PRIMARY_USER_APP_EXCLUDE_KEY =
             "VPNAPPEXCLUDED_27_com.testvpn.vpn";
     static final String PKGS_BYTES = getPackageByteString(List.of(PKGS));
     private static final Range<Integer> PRIMARY_USER_RANGE = uidRangeForUser(PRIMARY_USER.id);
+    private static final int TEST_KEEPALIVE_TIMER = 800;
+    private static final int TEST_SUB_ID = 1234;
+    private static final String TEST_MCCMNC = "12345";
 
     @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext;
     @Mock private UserManager mUserManager;
@@ -266,17 +305,57 @@
     @Mock private Vpn.VpnNetworkAgentWrapper mMockNetworkAgent;
     @Mock private ConnectivityManager mConnectivityManager;
     @Mock private ConnectivityDiagnosticsManager mCdm;
+    @Mock private TelephonyManager mTelephonyManager;
+    @Mock private TelephonyManager mTmPerSub;
+    @Mock private CarrierConfigManager mConfigManager;
+    @Mock private SubscriptionManager mSubscriptionManager;
     @Mock private IpSecService mIpSecService;
     @Mock private VpnProfileStore mVpnProfileStore;
-    @Mock private ScheduledThreadPoolExecutor mExecutor;
-    @Mock private ScheduledFuture mScheduledFuture;
+    private final TestExecutor mExecutor;
     @Mock DeviceIdleInternal mDeviceIdleInternal;
     private final VpnProfile mVpnProfile;
 
     private IpSecManager mIpSecManager;
-
     private TestDeps mTestDeps;
 
+    public static class TestExecutor extends ScheduledThreadPoolExecutor {
+        public static final long REAL_DELAY = -1;
+
+        // For the purposes of the test, run all scheduled tasks after 10ms to save
+        // execution time, unless overridden by the specific test. Set to REAL_DELAY
+        // to actually wait for the delay specified by the real call to schedule().
+        public long delayMs = 10;
+        // If this is true, execute() will call the runnable inline. This is useful because
+        // super.execute() calls schedule(), which messes with checks that scheduled() is
+        // called a given number of times.
+        public boolean executeDirect = false;
+
+        public TestExecutor() {
+            super(1);
+        }
+
+        @Override
+        public void execute(final Runnable command) {
+            // See |executeDirect| for why this is necessary.
+            if (executeDirect) {
+                command.run();
+            } else {
+                super.execute(command);
+            }
+        }
+
+        @Override
+        public ScheduledFuture<?> schedule(final Runnable command, final long delay,
+                TimeUnit unit) {
+            if (0 == delay || delayMs == REAL_DELAY) {
+                // super.execute() calls schedule() with 0, so use the real delay if it's 0.
+                return super.schedule(command, delay, unit);
+            } else {
+                return super.schedule(command, delayMs, TimeUnit.MILLISECONDS);
+            }
+        }
+    }
+
     public VpnTest() throws Exception {
         // Build an actual VPN profile that is capable of being converted to and from an
         // Ikev2VpnProfile
@@ -284,6 +363,7 @@
                 new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY);
         builder.setAuthPsk(TEST_VPN_PSK);
         builder.setBypassable(true /* isBypassable */);
+        mExecutor = spy(new TestExecutor());
         mVpnProfile = builder.build().toVpnProfile();
     }
 
@@ -310,6 +390,11 @@
         mockService(IpSecManager.class, Context.IPSEC_SERVICE, mIpSecManager);
         mockService(ConnectivityDiagnosticsManager.class, Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
                 mCdm);
+        mockService(TelephonyManager.class, Context.TELEPHONY_SERVICE, mTelephonyManager);
+        mockService(CarrierConfigManager.class, Context.CARRIER_CONFIG_SERVICE, mConfigManager);
+        mockService(SubscriptionManager.class, Context.TELEPHONY_SUBSCRIPTION_SERVICE,
+                mSubscriptionManager);
+        doReturn(mTmPerSub).when(mTelephonyManager).createForSubscriptionId(anyInt());
         when(mContext.getString(R.string.config_customVpnAlwaysOnDisconnectedDialogComponent))
                 .thenReturn(Resources.getSystem().getString(
                         R.string.config_customVpnAlwaysOnDisconnectedDialogComponent));
@@ -344,7 +429,6 @@
 
         // Set up mIkev2SessionCreator and mExecutor
         resetIkev2SessionCreator(mIkeSessionWrapper);
-        resetExecutor(mScheduledFuture);
     }
 
     private void resetIkev2SessionCreator(Vpn.IkeSessionWrapper ikeSession) {
@@ -353,23 +437,6 @@
                 .thenReturn(ikeSession);
     }
 
-    private void resetExecutor(ScheduledFuture scheduledFuture) {
-        doAnswer(
-                (invocation) -> {
-                    ((Runnable) invocation.getArgument(0)).run();
-                    return null;
-                })
-            .when(mExecutor)
-            .execute(any());
-        when(mExecutor.schedule(
-                any(Runnable.class), anyLong(), any())).thenReturn(mScheduledFuture);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(any());
-    }
-
     private <T> void mockService(Class<T> clazz, String name, T service) {
         doReturn(service).when(mContext).getSystemService(name);
         doReturn(name).when(mContext).getSystemServiceName(clazz);
@@ -480,9 +547,9 @@
     }
 
     private void verifyPowerSaveTempWhitelistApp(String packageName) {
-        verify(mDeviceIdleInternal).addPowerSaveTempWhitelistApp(anyInt(), eq(packageName),
-                anyLong(), anyInt(), eq(false), eq(PowerWhitelistManager.REASON_VPN),
-                eq("VpnManager event"));
+        verify(mDeviceIdleInternal, timeout(TEST_TIMEOUT_MS)).addPowerSaveTempWhitelistApp(
+                anyInt(), eq(packageName), anyLong(), anyInt(), eq(false),
+                eq(PowerWhitelistManager.REASON_VPN), eq("VpnManager event"));
     }
 
     @Test
@@ -721,7 +788,8 @@
     @Test
     public void testPrepare_throwSecurityExceptionWhenGivenPackageDoesNotBelongToTheCaller()
             throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks();
+        mTestDeps.mIgnoreCallingUidChecks = false;
+        final Vpn vpn = createVpn();
         assertThrows(SecurityException.class,
                 () -> vpn.prepare("com.not.vpn.owner", null, VpnManager.TYPE_VPN_SERVICE));
         assertThrows(SecurityException.class,
@@ -733,7 +801,7 @@
 
     @Test
     public void testPrepare_bothOldPackageAndNewPackageAreNull() throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks();
+        final Vpn vpn = createVpn();
         assertTrue(vpn.prepare(null, null, VpnManager.TYPE_VPN_SERVICE));
 
     }
@@ -816,17 +884,14 @@
         assertEquals(expected, vpn.getProfileNameForPackage(TEST_VPN_PKG));
     }
 
-    private Vpn createVpnAndSetupUidChecks(String... grantedOps) throws Exception {
-        return createVpnAndSetupUidChecks(PRIMARY_USER, grantedOps);
+    private Vpn createVpn(String... grantedOps) throws Exception {
+        return createVpn(PRIMARY_USER, grantedOps);
     }
 
-    private Vpn createVpnAndSetupUidChecks(UserInfo user, String... grantedOps) throws Exception {
+    private Vpn createVpn(UserInfo user, String... grantedOps) throws Exception {
         final Vpn vpn = createVpn(user.id);
         setMockedUsers(user);
 
-        when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt()))
-                .thenReturn(Process.myUid());
-
         for (final String opStr : grantedOps) {
             when(mAppOps.noteOpNoThrow(opStr, Process.myUid(), TEST_VPN_PKG,
                     null /* attributionTag */, null /* message */))
@@ -855,7 +920,7 @@
     public void testProvisionVpnProfileNoIpsecTunnels() throws Exception {
         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS))
                 .thenReturn(false);
-        final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
 
         try {
             checkProvisionVpnProfile(
@@ -866,7 +931,7 @@
     }
 
     private Vpn prepareVpnForVerifyAppExclusionList() throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
         when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
                 .thenReturn(mVpnProfile.encode());
         when(mVpnProfileStore.get(PRIMARY_USER_APP_EXCLUDE_KEY))
@@ -982,7 +1047,7 @@
 
     @Test
     public void testProvisionVpnProfilePreconsented() throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
 
         checkProvisionVpnProfile(
                 vpn, true /* expectedResult */, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
@@ -990,7 +1055,7 @@
 
     @Test
     public void testProvisionVpnProfileNotPreconsented() throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks();
+        final Vpn vpn = createVpn();
 
         // Expect that both the ACTIVATE_VPN and ACTIVATE_PLATFORM_VPN were tried, but the caller
         // had neither.
@@ -1000,14 +1065,14 @@
 
     @Test
     public void testProvisionVpnProfileVpnServicePreconsented() throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_VPN);
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_VPN);
 
         checkProvisionVpnProfile(vpn, true /* expectedResult */, AppOpsManager.OPSTR_ACTIVATE_VPN);
     }
 
     @Test
     public void testProvisionVpnProfileTooLarge() throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
 
         final VpnProfile bigProfile = new VpnProfile("");
         bigProfile.name = new String(new byte[Vpn.MAX_VPN_PROFILE_SIZE_BYTES + 1]);
@@ -1022,7 +1087,7 @@
     @Test
     public void testProvisionVpnProfileRestrictedUser() throws Exception {
         final Vpn vpn =
-                createVpnAndSetupUidChecks(
+                createVpn(
                         RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
 
         try {
@@ -1034,7 +1099,7 @@
 
     @Test
     public void testDeleteVpnProfile() throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks();
+        final Vpn vpn = createVpn();
 
         vpn.deleteVpnProfile(TEST_VPN_PKG);
 
@@ -1045,7 +1110,7 @@
     @Test
     public void testDeleteVpnProfileRestrictedUser() throws Exception {
         final Vpn vpn =
-                createVpnAndSetupUidChecks(
+                createVpn(
                         RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
 
         try {
@@ -1057,7 +1122,7 @@
 
     @Test
     public void testGetVpnProfilePrivileged() throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks();
+        final Vpn vpn = createVpn();
 
         when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
                 .thenReturn(new VpnProfile("").encode());
@@ -1076,7 +1141,7 @@
                 eq(null) /* message */);
         verify(mAppOps).startOp(
                 eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
-                eq(Process.myUid()),
+                eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
                 eq(packageName),
                 eq(null) /* attributionTag */,
                 eq(null) /* message */);
@@ -1086,14 +1151,14 @@
         // Add a small delay to double confirm that finishOp is only called once.
         verify(mAppOps, after(100)).finishOp(
                 eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
-                eq(Process.myUid()),
+                eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
                 eq(packageName),
                 eq(null) /* attributionTag */);
     }
 
     @Test
     public void testStartVpnProfile() throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
 
         when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
                 .thenReturn(mVpnProfile.encode());
@@ -1106,7 +1171,7 @@
 
     @Test
     public void testStartVpnProfileVpnServicePreconsented() throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_VPN);
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_VPN);
 
         when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
                 .thenReturn(mVpnProfile.encode());
@@ -1120,7 +1185,7 @@
 
     @Test
     public void testStartVpnProfileNotConsented() throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks();
+        final Vpn vpn = createVpn();
 
         try {
             vpn.startVpnProfile(TEST_VPN_PKG);
@@ -1145,7 +1210,7 @@
 
     @Test
     public void testStartVpnProfileMissingProfile() throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
 
         when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))).thenReturn(null);
 
@@ -1167,9 +1232,7 @@
 
     @Test
     public void testStartVpnProfileRestrictedUser() throws Exception {
-        final Vpn vpn =
-                createVpnAndSetupUidChecks(
-                        RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        final Vpn vpn = createVpn(RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
 
         try {
             vpn.startVpnProfile(TEST_VPN_PKG);
@@ -1180,9 +1243,7 @@
 
     @Test
     public void testStopVpnProfileRestrictedUser() throws Exception {
-        final Vpn vpn =
-                createVpnAndSetupUidChecks(
-                        RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        final Vpn vpn = createVpn(RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
 
         try {
             vpn.stopVpnProfile(TEST_VPN_PKG);
@@ -1193,7 +1254,7 @@
 
     @Test
     public void testStartOpAndFinishOpWillBeCalledWhenPlatformVpnIsOnAndOff() throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
         when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
                 .thenReturn(mVpnProfile.encode());
         vpn.startVpnProfile(TEST_VPN_PKG);
@@ -1201,14 +1262,14 @@
         // Add a small delay to make sure that startOp is only called once.
         verify(mAppOps, after(100).times(1)).startOp(
                 eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
-                eq(Process.myUid()),
+                eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
                 eq(TEST_VPN_PKG),
                 eq(null) /* attributionTag */,
                 eq(null) /* message */);
         // Check that the startOp is not called with OPSTR_ESTABLISH_VPN_SERVICE.
         verify(mAppOps, never()).startOp(
                 eq(AppOpsManager.OPSTR_ESTABLISH_VPN_SERVICE),
-                eq(Process.myUid()),
+                eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
                 eq(TEST_VPN_PKG),
                 eq(null) /* attributionTag */,
                 eq(null) /* message */);
@@ -1218,7 +1279,9 @@
 
     @Test
     public void testStartOpWithSeamlessHandover() throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_VPN);
+        // Create with SYSTEM_USER so that establish() will match the user ID when checking
+        // against Binder.getCallerUid
+        final Vpn vpn = createVpn(SYSTEM_USER, AppOpsManager.OPSTR_ACTIVATE_VPN);
         assertTrue(vpn.prepare(TEST_VPN_PKG, null, VpnManager.TYPE_VPN_SERVICE));
         final VpnConfig config = new VpnConfig();
         config.user = "VpnTest";
@@ -1249,12 +1312,12 @@
     }
 
     private void verifyVpnManagerEvent(String sessionKey, String category, int errorClass,
-            int errorCode, String[] packageName, VpnProfileState... profileState) {
+            int errorCode, String[] packageName, @NonNull VpnProfileState... profileState) {
         final Context userContext =
                 mContext.createContextAsUser(UserHandle.of(PRIMARY_USER.id), 0 /* flags */);
         final ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
 
-        final int verifyTimes = (profileState == null) ? 1 : profileState.length;
+        final int verifyTimes = profileState.length;
         verify(userContext, times(verifyTimes)).startService(intentArgumentCaptor.capture());
 
         for (int i = 0; i < verifyTimes; i++) {
@@ -1285,10 +1348,8 @@
                         VpnManager.EXTRA_UNDERLYING_LINK_PROPERTIES));
             }
 
-            if (profileState != null) {
-                assertEquals(profileState[i], intent.getParcelableExtra(
-                        VpnManager.EXTRA_VPN_PROFILE_STATE, VpnProfileState.class));
-            }
+            assertEquals(profileState[i], intent.getParcelableExtra(
+                    VpnManager.EXTRA_VPN_PROFILE_STATE, VpnProfileState.class));
         }
         reset(userContext);
     }
@@ -1297,7 +1358,11 @@
         // CATEGORY_EVENT_DEACTIVATED_BY_USER is not an error event, so both of errorClass and
         // errorCode won't be set.
         verifyVpnManagerEvent(sessionKey, VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
-                -1 /* errorClass */, -1 /* errorCode */, packageName, null /* profileState */);
+                -1 /* errorClass */, -1 /* errorCode */, packageName,
+                // VPN NetworkAgnet does not switch to CONNECTED in the test, and the state is not
+                // important here. Verify that the state as it is, i.e. CONNECTING state.
+                new VpnProfileState(VpnProfileState.STATE_CONNECTING,
+                        sessionKey, false /* alwaysOn */, false /* lockdown */));
     }
 
     private void verifyAlwaysOnStateChanged(String[] packageName, VpnProfileState... profileState) {
@@ -1314,7 +1379,7 @@
         // this is checked with CONTROL_VPN so simulate holding CONTROL_VPN in order to pass the
         // security checks.
         doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
-        final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
         when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
                 .thenReturn(mVpnProfile.encode());
 
@@ -1406,7 +1471,7 @@
 
     @Test
     public void testReconnectVpnManagerVpnWithAlwaysOnEnabled() throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
         when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
                 .thenReturn(mVpnProfile.encode());
         vpn.startVpnProfile(TEST_VPN_PKG);
@@ -1430,51 +1495,83 @@
     }
 
     @Test
+    public void testLockdown_enableDisableWhileConnected() throws Exception {
+        final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
+                createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
+
+        final InOrder order = inOrder(mTestDeps);
+        order.verify(mTestDeps, timeout(TIMEOUT_CROSSTHREAD_MS))
+                .newNetworkAgent(any(), any(), any(), any(), any(), any(),
+                        argThat(config -> config.allowBypass), any(), any());
+
+        // Make VPN lockdown.
+        assertTrue(vpnSnapShot.vpn.setAlwaysOnPackage(TEST_VPN_PKG, true /* lockdown */,
+                null /* lockdownAllowlist */));
+
+        order.verify(mTestDeps, timeout(TIMEOUT_CROSSTHREAD_MS))
+                .newNetworkAgent(any(), any(), any(), any(), any(), any(),
+                argThat(config -> !config.allowBypass), any(), any());
+
+        // Disable lockdown.
+        assertTrue(vpnSnapShot.vpn.setAlwaysOnPackage(TEST_VPN_PKG, false /* lockdown */,
+                null /* lockdownAllowlist */));
+
+        order.verify(mTestDeps, timeout(TIMEOUT_CROSSTHREAD_MS))
+                .newNetworkAgent(any(), any(), any(), any(), any(), any(),
+                        argThat(config -> config.allowBypass), any(), any());
+    }
+
+    @Test
     public void testSetPackageAuthorizationVpnService() throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks();
+        final Vpn vpn = createVpn();
 
         assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_SERVICE));
         verify(mAppOps)
                 .setMode(
                         eq(AppOpsManager.OPSTR_ACTIVATE_VPN),
-                        eq(Process.myUid()),
+                        eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
                         eq(TEST_VPN_PKG),
                         eq(AppOpsManager.MODE_ALLOWED));
     }
 
     @Test
     public void testSetPackageAuthorizationPlatformVpn() throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks();
+        final Vpn vpn = createVpn();
 
         assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, TYPE_VPN_PLATFORM));
         verify(mAppOps)
                 .setMode(
                         eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
-                        eq(Process.myUid()),
+                        eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
                         eq(TEST_VPN_PKG),
                         eq(AppOpsManager.MODE_ALLOWED));
     }
 
     @Test
     public void testSetPackageAuthorizationRevokeAuthorization() throws Exception {
-        final Vpn vpn = createVpnAndSetupUidChecks();
+        final Vpn vpn = createVpn();
 
         assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_NONE));
         verify(mAppOps)
                 .setMode(
                         eq(AppOpsManager.OPSTR_ACTIVATE_VPN),
-                        eq(Process.myUid()),
+                        eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
                         eq(TEST_VPN_PKG),
                         eq(AppOpsManager.MODE_IGNORED));
         verify(mAppOps)
                 .setMode(
                         eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
-                        eq(Process.myUid()),
+                        eq(UserHandle.getUid(PRIMARY_USER.id, Process.myUid())),
                         eq(TEST_VPN_PKG),
                         eq(AppOpsManager.MODE_IGNORED));
     }
 
     private NetworkCallback triggerOnAvailableAndGetCallback() throws Exception {
+        return triggerOnAvailableAndGetCallback(new NetworkCapabilities.Builder().build());
+    }
+
+    private NetworkCallback triggerOnAvailableAndGetCallback(
+            @NonNull final NetworkCapabilities caps) throws Exception {
         final ArgumentCaptor<NetworkCallback> networkCallbackCaptor =
                 ArgumentCaptor.forClass(NetworkCallback.class);
         verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
@@ -1491,7 +1588,7 @@
         // if NetworkCapabilities and LinkProperties of underlying network will be sent/cleared or
         // not.
         // See verifyVpnManagerEvent().
-        cb.onCapabilitiesChanged(TEST_NETWORK, new NetworkCapabilities());
+        cb.onCapabilitiesChanged(TEST_NETWORK, caps);
         cb.onLinkPropertiesChanged(TEST_NETWORK, new LinkProperties());
         return cb;
     }
@@ -1507,7 +1604,7 @@
         final ArgumentCaptor<IkeSessionCallback> captor =
                 ArgumentCaptor.forClass(IkeSessionCallback.class);
 
-        final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
         when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
                 .thenReturn(mVpnProfile.encode());
 
@@ -1530,10 +1627,7 @@
         // same process with the real case.
         if (errorCode == VpnManager.ERROR_CODE_NETWORK_LOST) {
             cb.onLost(TEST_NETWORK);
-            final ArgumentCaptor<Runnable> runnableCaptor =
-                    ArgumentCaptor.forClass(Runnable.class);
-            verify(mExecutor).schedule(runnableCaptor.capture(), anyLong(), any());
-            runnableCaptor.getValue().run();
+            verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any());
         } else {
             final IkeSessionCallback ikeCb = captor.getValue();
             ikeCb.onClosedWithException(exception);
@@ -1542,7 +1636,10 @@
         verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
         reset(mDeviceIdleInternal);
         verifyVpnManagerEvent(sessionKey, category, errorType, errorCode,
-                new String[] {TEST_VPN_PKG}, null /* profileState */);
+                // VPN NetworkAgnet does not switch to CONNECTED in the test, and the state is not
+                // important here. Verify that the state as it is, i.e. CONNECTING state.
+                new String[] {TEST_VPN_PKG}, new VpnProfileState(VpnProfileState.STATE_CONNECTING,
+                        sessionKey, false /* alwaysOn */, false /* lockdown */));
         if (errorType == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
             verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
                     .unregisterNetworkCallback(eq(cb));
@@ -1558,25 +1655,23 @@
     }
 
     private IkeSessionCallback verifyRetryAndGetNewIkeCb(int retryIndex) {
-        final ArgumentCaptor<Runnable> runnableCaptor =
-                ArgumentCaptor.forClass(Runnable.class);
         final ArgumentCaptor<IkeSessionCallback> ikeCbCaptor =
                 ArgumentCaptor.forClass(IkeSessionCallback.class);
 
         // Verify retry is scheduled
-        final long expectedDelay = mTestDeps.getNextRetryDelaySeconds(retryIndex);
-        verify(mExecutor).schedule(runnableCaptor.capture(), eq(expectedDelay), any());
+        final long expectedDelayMs = mTestDeps.getNextRetryDelayMs(retryIndex);
+        final ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class);
+        verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), delayCaptor.capture(),
+                eq(TimeUnit.MILLISECONDS));
+        final List<Long> delays = delayCaptor.getAllValues();
+        assertEquals(expectedDelayMs, (long) delays.get(delays.size() - 1));
 
-        // Mock the event of firing the retry task
-        runnableCaptor.getValue().run();
-
-        verify(mIkev2SessionCreator)
+        verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS + expectedDelayMs))
                 .createIkeSession(any(), any(), any(), any(), ikeCbCaptor.capture(), any());
 
         // Forget the mIkev2SessionCreator#createIkeSession call and mExecutor#schedule call
         // for the next retry verification
         resetIkev2SessionCreator(mIkeSessionWrapper);
-        resetExecutor(mScheduledFuture);
 
         return ikeCbCaptor.getValue();
     }
@@ -1812,6 +1907,21 @@
 
     private PlatformVpnSnapshot verifySetupPlatformVpn(
             IkeSessionConfiguration ikeConfig, boolean mtuSupportsIpv6) throws Exception {
+        return verifySetupPlatformVpn(mVpnProfile, ikeConfig, mtuSupportsIpv6);
+    }
+
+    private PlatformVpnSnapshot verifySetupPlatformVpn(VpnProfile vpnProfile,
+            IkeSessionConfiguration ikeConfig, boolean mtuSupportsIpv6) throws Exception {
+        return verifySetupPlatformVpn(vpnProfile, ikeConfig,
+                new NetworkCapabilities.Builder().build() /* underlying network caps */,
+                mtuSupportsIpv6, false /* areLongLivedTcpConnectionsExpensive */);
+    }
+
+    private PlatformVpnSnapshot verifySetupPlatformVpn(VpnProfile vpnProfile,
+            IkeSessionConfiguration ikeConfig,
+            @NonNull final NetworkCapabilities underlyingNetworkCaps,
+            boolean mtuSupportsIpv6,
+            boolean areLongLivedTcpConnectionsExpensive) throws Exception {
         if (!mtuSupportsIpv6) {
             doReturn(IPV6_MIN_MTU - 1).when(mTestDeps).calculateVpnMtu(any(), anyInt(), anyInt(),
                     anyBoolean());
@@ -1820,13 +1930,16 @@
         doReturn(mMockNetworkAgent).when(mTestDeps)
                 .newNetworkAgent(
                         any(), any(), anyString(), any(), any(), any(), any(), any(), any());
+        doReturn(TEST_NETWORK).when(mMockNetworkAgent).getNetwork();
 
-        final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
         when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
-                .thenReturn(mVpnProfile.encode());
+                .thenReturn(vpnProfile.encode());
 
         vpn.startVpnProfile(TEST_VPN_PKG);
-        final NetworkCallback nwCb = triggerOnAvailableAndGetCallback();
+        final NetworkCallback nwCb = triggerOnAvailableAndGetCallback(underlyingNetworkCaps);
+        verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any());
+        reset(mExecutor);
 
         // Mock the setup procedure by firing callbacks
         final Pair<IkeSessionCallback, ChildSessionCallback> cbPair =
@@ -1850,7 +1963,7 @@
         verify(mTestDeps).newNetworkAgent(
                 any(), any(), anyString(), ncCaptor.capture(), lpCaptor.capture(),
                 any(), nacCaptor.capture(), any(), any());
-
+        verify(mIkeSessionWrapper).setUnderpinnedNetwork(TEST_NETWORK);
         // Check LinkProperties
         final LinkProperties lp = lpCaptor.getValue();
         final List<RouteInfo> expectedRoutes =
@@ -1893,8 +2006,10 @@
 
         // Check if allowBypass is set or not.
         assertTrue(nacCaptor.getValue().isBypassableVpn());
-        assertTrue(((VpnTransportInfo) ncCaptor.getValue().getTransportInfo()).getBypassable());
-
+        final VpnTransportInfo info = (VpnTransportInfo) ncCaptor.getValue().getTransportInfo();
+        assertTrue(info.isBypassable());
+        assertEquals(areLongLivedTcpConnectionsExpensive,
+                info.areLongLivedTcpConnectionsExpensive());
         return new PlatformVpnSnapshot(vpn, nwCb, ikeCb, childCb);
     }
 
@@ -1906,6 +2021,441 @@
     }
 
     @Test
+    public void testMigrateIkeSession_FromIkeTunnConnParams_AutoTimerNoTimer() throws Exception {
+        doTestMigrateIkeSession_FromIkeTunnConnParams(
+                false /* isAutomaticIpVersionSelectionEnabled */,
+                true /* isAutomaticNattKeepaliveTimerEnabled */,
+                TEST_KEEPALIVE_TIMEOUT_UNSET /* keepaliveInProfile */,
+                ESP_IP_VERSION_AUTO /* ipVersionInProfile */,
+                ESP_ENCAP_TYPE_AUTO /* encapTypeInProfile */);
+    }
+
+    @Test
+    public void testMigrateIkeSession_FromIkeTunnConnParams_AutoTimerTimerSet() throws Exception {
+        doTestMigrateIkeSession_FromIkeTunnConnParams(
+                false /* isAutomaticIpVersionSelectionEnabled */,
+                true /* isAutomaticNattKeepaliveTimerEnabled */,
+                TEST_KEEPALIVE_TIMER /* keepaliveInProfile */,
+                ESP_IP_VERSION_AUTO /* ipVersionInProfile */,
+                ESP_ENCAP_TYPE_AUTO /* encapTypeInProfile */);
+    }
+
+    @Test
+    public void testMigrateIkeSession_FromIkeTunnConnParams_AutoIp() throws Exception {
+        doTestMigrateIkeSession_FromIkeTunnConnParams(
+                true /* isAutomaticIpVersionSelectionEnabled */,
+                false /* isAutomaticNattKeepaliveTimerEnabled */,
+                TEST_KEEPALIVE_TIMEOUT_UNSET /* keepaliveInProfile */,
+                ESP_IP_VERSION_AUTO /* ipVersionInProfile */,
+                ESP_ENCAP_TYPE_AUTO /* encapTypeInProfile */);
+    }
+
+    @Test
+    public void testMigrateIkeSession_FromIkeTunnConnParams_AssignedIpProtocol() throws Exception {
+        doTestMigrateIkeSession_FromIkeTunnConnParams(
+                false /* isAutomaticIpVersionSelectionEnabled */,
+                false /* isAutomaticNattKeepaliveTimerEnabled */,
+                TEST_KEEPALIVE_TIMEOUT_UNSET /* keepaliveInProfile */,
+                ESP_IP_VERSION_IPV4 /* ipVersionInProfile */,
+                ESP_ENCAP_TYPE_UDP /* encapTypeInProfile */);
+    }
+
+    @Test
+    public void testMigrateIkeSession_FromNotIkeTunnConnParams_AutoTimer() throws Exception {
+        doTestMigrateIkeSession_FromNotIkeTunnConnParams(
+                false /* isAutomaticIpVersionSelectionEnabled */,
+                true /* isAutomaticNattKeepaliveTimerEnabled */);
+    }
+
+    @Test
+    public void testMigrateIkeSession_FromNotIkeTunnConnParams_AutoIp() throws Exception {
+        doTestMigrateIkeSession_FromNotIkeTunnConnParams(
+                true /* isAutomaticIpVersionSelectionEnabled */,
+                false /* isAutomaticNattKeepaliveTimerEnabled */);
+    }
+
+    private void doTestMigrateIkeSession_FromNotIkeTunnConnParams(
+            boolean isAutomaticIpVersionSelectionEnabled,
+            boolean isAutomaticNattKeepaliveTimerEnabled) throws Exception {
+        final Ikev2VpnProfile ikeProfile =
+                new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY)
+                        .setAuthPsk(TEST_VPN_PSK)
+                        .setBypassable(true /* isBypassable */)
+                        .setAutomaticNattKeepaliveTimerEnabled(isAutomaticNattKeepaliveTimerEnabled)
+                        .setAutomaticIpVersionSelectionEnabled(isAutomaticIpVersionSelectionEnabled)
+                        .build();
+
+        final int expectedKeepalive = isAutomaticNattKeepaliveTimerEnabled
+                ? AUTOMATIC_KEEPALIVE_DELAY_SECONDS
+                : DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT;
+        doTestMigrateIkeSession(ikeProfile.toVpnProfile(),
+                expectedKeepalive,
+                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
+                new NetworkCapabilities.Builder().build());
+    }
+
+    private Ikev2VpnProfile makeIkeV2VpnProfile(
+            boolean isAutomaticIpVersionSelectionEnabled,
+            boolean isAutomaticNattKeepaliveTimerEnabled,
+            int keepaliveInProfile,
+            int ipVersionInProfile,
+            int encapTypeInProfile) {
+        // TODO: Update helper function in IkeSessionTestUtils to support building IkeSessionParams
+        // with IP version and encap type when mainline-prod branch support these two APIs.
+        final IkeSessionParams params = getTestIkeSessionParams(true /* testIpv6 */,
+                new IkeFqdnIdentification(TEST_IDENTITY), keepaliveInProfile);
+        final IkeSessionParams ikeSessionParams = new IkeSessionParams.Builder(params)
+                .setIpVersion(ipVersionInProfile)
+                .setEncapType(encapTypeInProfile)
+                .build();
+
+        final IkeTunnelConnectionParams tunnelParams =
+                new IkeTunnelConnectionParams(ikeSessionParams, CHILD_PARAMS);
+        return new Ikev2VpnProfile.Builder(tunnelParams)
+                .setBypassable(true)
+                .setAutomaticNattKeepaliveTimerEnabled(isAutomaticNattKeepaliveTimerEnabled)
+                .setAutomaticIpVersionSelectionEnabled(isAutomaticIpVersionSelectionEnabled)
+                .build();
+    }
+
+    private void doTestMigrateIkeSession_FromIkeTunnConnParams(
+            boolean isAutomaticIpVersionSelectionEnabled,
+            boolean isAutomaticNattKeepaliveTimerEnabled,
+            int keepaliveInProfile,
+            int ipVersionInProfile,
+            int encapTypeInProfile) throws Exception {
+        doTestMigrateIkeSession_FromIkeTunnConnParams(isAutomaticIpVersionSelectionEnabled,
+                isAutomaticNattKeepaliveTimerEnabled, keepaliveInProfile, ipVersionInProfile,
+                encapTypeInProfile, new NetworkCapabilities.Builder().build());
+    }
+
+    private void doTestMigrateIkeSession_FromIkeTunnConnParams(
+            boolean isAutomaticIpVersionSelectionEnabled,
+            boolean isAutomaticNattKeepaliveTimerEnabled,
+            int keepaliveInProfile,
+            int ipVersionInProfile,
+            int encapTypeInProfile,
+            @NonNull final NetworkCapabilities nc) throws Exception {
+        final Ikev2VpnProfile ikeProfile = makeIkeV2VpnProfile(
+                isAutomaticIpVersionSelectionEnabled,
+                isAutomaticNattKeepaliveTimerEnabled,
+                keepaliveInProfile,
+                ipVersionInProfile,
+                encapTypeInProfile);
+
+        final IkeSessionParams ikeSessionParams =
+                ikeProfile.getIkeTunnelConnectionParams().getIkeSessionParams();
+        final int expectedKeepalive = isAutomaticNattKeepaliveTimerEnabled
+                ? AUTOMATIC_KEEPALIVE_DELAY_SECONDS
+                : ikeSessionParams.getNattKeepAliveDelaySeconds();
+        final int expectedIpVersion = isAutomaticIpVersionSelectionEnabled
+                ? ESP_IP_VERSION_AUTO
+                : ikeSessionParams.getIpVersion();
+        final int expectedEncapType = isAutomaticIpVersionSelectionEnabled
+                ? ESP_ENCAP_TYPE_AUTO
+                : ikeSessionParams.getEncapType();
+        doTestMigrateIkeSession(ikeProfile.toVpnProfile(), expectedKeepalive,
+                expectedIpVersion, expectedEncapType, nc);
+    }
+
+    @Test
+    public void doTestMigrateIkeSession_Vcn() throws Exception {
+        final int expectedKeepalive = 2097; // Any unlikely number will do
+        final NetworkCapabilities vcnNc = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .setTransportInfo(new VcnTransportInfo(TEST_SUB_ID, expectedKeepalive))
+                .build();
+        final Ikev2VpnProfile ikev2VpnProfile = makeIkeV2VpnProfile(
+                true /* isAutomaticIpVersionSelectionEnabled */,
+                true /* isAutomaticNattKeepaliveTimerEnabled */,
+                234 /* keepaliveInProfile */, // Should be ignored, any value will do
+                ESP_IP_VERSION_IPV4, // Should be ignored
+                ESP_ENCAP_TYPE_UDP // Should be ignored
+        );
+        doTestMigrateIkeSession(
+                ikev2VpnProfile.toVpnProfile(),
+                expectedKeepalive,
+                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
+                vcnNc);
+    }
+
+    private void doTestMigrateIkeSession(
+            @NonNull final VpnProfile profile,
+            final int expectedKeepalive,
+            final int expectedIpVersion,
+            final int expectedEncapType,
+            @NonNull final NetworkCapabilities caps) throws Exception {
+        final PlatformVpnSnapshot vpnSnapShot =
+                verifySetupPlatformVpn(profile,
+                        createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
+                        caps /* underlying network capabilities */,
+                        false /* mtuSupportsIpv6 */,
+                        expectedKeepalive < DEFAULT_LONG_LIVED_TCP_CONNS_EXPENSIVE_TIMEOUT_SEC);
+        // Simulate a new network coming up
+        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
+        verify(mIkeSessionWrapper, never()).setNetwork(any(), anyInt(), anyInt(), anyInt());
+
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2, caps);
+        // Verify MOBIKE is triggered
+        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(TEST_NETWORK_2,
+                expectedIpVersion, expectedEncapType, expectedKeepalive);
+
+        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
+    }
+
+    @Test
+    public void testLinkPropertiesUpdateTriggerReevaluation() throws Exception {
+        final boolean hasV6 = true;
+
+        mockCarrierConfig(TEST_SUB_ID, TelephonyManager.SIM_STATE_LOADED, TEST_KEEPALIVE_TIMER,
+                PREFERRED_IKE_PROTOCOL_IPV6_ESP);
+        final IkeSessionParams params = getTestIkeSessionParams(hasV6,
+                new IkeFqdnIdentification(TEST_IDENTITY), TEST_KEEPALIVE_TIMER);
+        final IkeTunnelConnectionParams tunnelParams =
+                new IkeTunnelConnectionParams(params, CHILD_PARAMS);
+        final Ikev2VpnProfile ikeProfile = new Ikev2VpnProfile.Builder(tunnelParams)
+                .setBypassable(true)
+                .setAutomaticNattKeepaliveTimerEnabled(false)
+                .setAutomaticIpVersionSelectionEnabled(true)
+                .build();
+        final PlatformVpnSnapshot vpnSnapShot =
+                verifySetupPlatformVpn(ikeProfile.toVpnProfile(),
+                        createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
+                        new NetworkCapabilities.Builder().build() /* underlying network caps */,
+                        hasV6 /* mtuSupportsIpv6 */,
+                        false /* areLongLivedTcpConnectionsExpensive */);
+        reset(mExecutor);
+
+        // Simulate a new network coming up
+        final LinkProperties lp = new LinkProperties();
+        lp.addLinkAddress(new LinkAddress("192.0.2.2/32"));
+
+        // Have the executor use the real delay to make sure schedule() was called only
+        // once for all calls. Also, arrange for execute() not to call schedule() to avoid
+        // messing with the checks for schedule().
+        mExecutor.delayMs = TestExecutor.REAL_DELAY;
+        mExecutor.executeDirect = true;
+        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
+        vpnSnapShot.nwCb.onCapabilitiesChanged(
+                TEST_NETWORK_2, new NetworkCapabilities.Builder().build());
+        vpnSnapShot.nwCb.onLinkPropertiesChanged(TEST_NETWORK_2, new LinkProperties(lp));
+        verify(mExecutor).schedule(any(Runnable.class), longThat(it -> it > 0), any());
+        reset(mExecutor);
+
+        final InOrder order = inOrder(mIkeSessionWrapper);
+
+        // Verify the network is started
+        order.verify(mIkeSessionWrapper, timeout(TIMEOUT_CROSSTHREAD_MS)).setNetwork(TEST_NETWORK_2,
+                ESP_IP_VERSION_AUTO, ESP_ENCAP_TYPE_AUTO, TEST_KEEPALIVE_TIMER);
+
+        // Send the same properties, check that no migration is scheduled
+        vpnSnapShot.nwCb.onLinkPropertiesChanged(TEST_NETWORK_2, new LinkProperties(lp));
+        verify(mExecutor, never()).schedule(any(Runnable.class), anyLong(), any());
+
+        // Add v6 address, verify MOBIKE is triggered
+        lp.addLinkAddress(new LinkAddress("2001:db8::1/64"));
+        vpnSnapShot.nwCb.onLinkPropertiesChanged(TEST_NETWORK_2, new LinkProperties(lp));
+        order.verify(mIkeSessionWrapper, timeout(TIMEOUT_CROSSTHREAD_MS)).setNetwork(TEST_NETWORK_2,
+                ESP_IP_VERSION_AUTO, ESP_ENCAP_TYPE_AUTO, TEST_KEEPALIVE_TIMER);
+
+        // Add another v4 address, verify MOBIKE is triggered
+        final LinkProperties stacked = new LinkProperties();
+        stacked.setInterfaceName("v4-" + lp.getInterfaceName());
+        stacked.addLinkAddress(new LinkAddress("192.168.0.1/32"));
+        lp.addStackedLink(stacked);
+        vpnSnapShot.nwCb.onLinkPropertiesChanged(TEST_NETWORK_2, new LinkProperties(lp));
+        order.verify(mIkeSessionWrapper, timeout(TIMEOUT_CROSSTHREAD_MS)).setNetwork(TEST_NETWORK_2,
+                ESP_IP_VERSION_AUTO, ESP_ENCAP_TYPE_AUTO, TEST_KEEPALIVE_TIMER);
+
+        vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
+    }
+
+    private void mockCarrierConfig(int subId, int simStatus, int keepaliveTimer, int ikeProtocol) {
+        final SubscriptionInfo subscriptionInfo = mock(SubscriptionInfo.class);
+        doReturn(subId).when(subscriptionInfo).getSubscriptionId();
+        doReturn(List.of(subscriptionInfo)).when(mSubscriptionManager)
+                .getActiveSubscriptionInfoList();
+
+        doReturn(simStatus).when(mTmPerSub).getSimApplicationState();
+        doReturn(TEST_MCCMNC).when(mTmPerSub).getSimOperator(subId);
+
+        final PersistableBundle persistableBundle = new PersistableBundle();
+        persistableBundle.putInt(KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT, keepaliveTimer);
+        persistableBundle.putInt(KEY_PREFERRED_IKE_PROTOCOL_INT, ikeProtocol);
+        // For CarrierConfigManager.isConfigForIdentifiedCarrier check
+        persistableBundle.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        doReturn(persistableBundle).when(mConfigManager).getConfigForSubId(subId);
+    }
+
+    private CarrierConfigManager.CarrierConfigChangeListener getCarrierConfigListener() {
+        final ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerCaptor =
+                ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
+
+        verify(mConfigManager).registerCarrierConfigChangeListener(any(), listenerCaptor.capture());
+
+        return listenerCaptor.getValue();
+    }
+
+    @Test
+    public void testNattKeepaliveTimerFromCarrierConfig_noSubId() throws Exception {
+        doTestReadCarrierConfig(new NetworkCapabilities(),
+                TelephonyManager.SIM_STATE_LOADED,
+                PREFERRED_IKE_PROTOCOL_IPV4_UDP,
+                AUTOMATIC_KEEPALIVE_DELAY_SECONDS /* expectedKeepaliveTimer */,
+                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
+                false /* expectedReadFromCarrierConfig*/,
+                true /* areLongLivedTcpConnectionsExpensive */);
+    }
+
+    @Test
+    public void testNattKeepaliveTimerFromCarrierConfig_simAbsent() throws Exception {
+        doTestReadCarrierConfig(new NetworkCapabilities.Builder().build(),
+                TelephonyManager.SIM_STATE_ABSENT,
+                PREFERRED_IKE_PROTOCOL_IPV4_UDP,
+                AUTOMATIC_KEEPALIVE_DELAY_SECONDS /* expectedKeepaliveTimer */,
+                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
+                false /* expectedReadFromCarrierConfig*/,
+                true /* areLongLivedTcpConnectionsExpensive */);
+    }
+
+    @Test
+    public void testNattKeepaliveTimerFromCarrierConfig() throws Exception {
+        doTestReadCarrierConfig(createTestCellNc(),
+                TelephonyManager.SIM_STATE_LOADED,
+                PREFERRED_IKE_PROTOCOL_AUTO,
+                TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */,
+                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
+                true /* expectedReadFromCarrierConfig*/,
+                false /* areLongLivedTcpConnectionsExpensive */);
+    }
+
+    @Test
+    public void testNattKeepaliveTimerFromCarrierConfig_NotCell() throws Exception {
+        final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_WIFI)
+                .setTransportInfo(new WifiInfo.Builder().build())
+                .build();
+        doTestReadCarrierConfig(nc,
+                TelephonyManager.SIM_STATE_LOADED,
+                PREFERRED_IKE_PROTOCOL_IPV4_UDP,
+                AUTOMATIC_KEEPALIVE_DELAY_SECONDS /* expectedKeepaliveTimer */,
+                ESP_IP_VERSION_AUTO /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_AUTO /* expectedEncapType */,
+                false /* expectedReadFromCarrierConfig*/,
+                true /* areLongLivedTcpConnectionsExpensive */);
+    }
+
+    @Test
+    public void testPreferredIpProtocolFromCarrierConfig_v4UDP() throws Exception {
+        doTestReadCarrierConfig(createTestCellNc(),
+                TelephonyManager.SIM_STATE_LOADED,
+                PREFERRED_IKE_PROTOCOL_IPV4_UDP,
+                TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */,
+                ESP_IP_VERSION_IPV4 /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_UDP /* expectedEncapType */,
+                true /* expectedReadFromCarrierConfig*/,
+                false /* areLongLivedTcpConnectionsExpensive */);
+    }
+
+    @Test
+    public void testPreferredIpProtocolFromCarrierConfig_v6ESP() throws Exception {
+        doTestReadCarrierConfig(createTestCellNc(),
+                TelephonyManager.SIM_STATE_LOADED,
+                PREFERRED_IKE_PROTOCOL_IPV6_ESP,
+                TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */,
+                ESP_IP_VERSION_IPV6 /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_NONE /* expectedEncapType */,
+                true /* expectedReadFromCarrierConfig*/,
+                false /* areLongLivedTcpConnectionsExpensive */);
+    }
+
+    @Test
+    public void testPreferredIpProtocolFromCarrierConfig_v6UDP() throws Exception {
+        doTestReadCarrierConfig(createTestCellNc(),
+                TelephonyManager.SIM_STATE_LOADED,
+                PREFERRED_IKE_PROTOCOL_IPV6_UDP,
+                TEST_KEEPALIVE_TIMER /* expectedKeepaliveTimer */,
+                ESP_IP_VERSION_IPV6 /* expectedIpVersion */,
+                ESP_ENCAP_TYPE_UDP /* expectedEncapType */,
+                true /* expectedReadFromCarrierConfig*/,
+                false /* areLongLivedTcpConnectionsExpensive */);
+    }
+
+    private NetworkCapabilities createTestCellNc() {
+        return new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+                        .setSubscriptionId(TEST_SUB_ID)
+                        .build())
+                .build();
+    }
+
+    private void doTestReadCarrierConfig(NetworkCapabilities nc, int simState, int preferredIpProto,
+            int expectedKeepaliveTimer, int expectedIpVersion, int expectedEncapType,
+            boolean expectedReadFromCarrierConfig,
+            boolean areLongLivedTcpConnectionsExpensive)
+            throws Exception {
+        final Ikev2VpnProfile ikeProfile =
+                new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY)
+                        .setAuthPsk(TEST_VPN_PSK)
+                        .setBypassable(true /* isBypassable */)
+                        .setAutomaticNattKeepaliveTimerEnabled(true)
+                        .setAutomaticIpVersionSelectionEnabled(true)
+                        .build();
+
+        final PlatformVpnSnapshot vpnSnapShot =
+                verifySetupPlatformVpn(ikeProfile.toVpnProfile(),
+                        createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
+                        new NetworkCapabilities.Builder().build() /* underlying network caps */,
+                        false /* mtuSupportsIpv6 */,
+                        true /* areLongLivedTcpConnectionsExpensive */);
+
+        final CarrierConfigManager.CarrierConfigChangeListener listener =
+                getCarrierConfigListener();
+
+        // Simulate a new network coming up
+        vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
+        // Migration will not be started until receiving network capabilities change.
+        verify(mIkeSessionWrapper, never()).setNetwork(any(), anyInt(), anyInt(), anyInt());
+
+        reset(mIkeSessionWrapper);
+        mockCarrierConfig(TEST_SUB_ID, simState, TEST_KEEPALIVE_TIMER, preferredIpProto);
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2, nc);
+        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(TEST_NETWORK_2,
+                expectedIpVersion, expectedEncapType, expectedKeepaliveTimer);
+        if (expectedReadFromCarrierConfig) {
+            final ArgumentCaptor<NetworkCapabilities> ncCaptor =
+                    ArgumentCaptor.forClass(NetworkCapabilities.class);
+            verify(mMockNetworkAgent).doSendNetworkCapabilities(ncCaptor.capture());
+
+            final VpnTransportInfo info =
+                    (VpnTransportInfo) ncCaptor.getValue().getTransportInfo();
+            assertEquals(areLongLivedTcpConnectionsExpensive,
+                    info.areLongLivedTcpConnectionsExpensive());
+        } else {
+            verify(mMockNetworkAgent, never()).doSendNetworkCapabilities(any());
+        }
+
+        reset(mExecutor);
+        reset(mIkeSessionWrapper);
+        reset(mMockNetworkAgent);
+
+        // Trigger carrier config change
+        listener.onCarrierConfigChanged(1 /* logicalSlotIndex */, TEST_SUB_ID,
+                -1 /* carrierId */, -1 /* specificCarrierId */);
+        verify(mIkeSessionWrapper).setNetwork(TEST_NETWORK_2,
+                expectedIpVersion, expectedEncapType, expectedKeepaliveTimer);
+        // Expect no NetworkCapabilities change.
+        // Call to doSendNetworkCapabilities() will not be triggered.
+        verify(mMockNetworkAgent, never()).doSendNetworkCapabilities(any());
+    }
+
+    @Test
     public void testStartPlatformVpn_mtuDoesNotSupportIpv6() throws Exception {
         final PlatformVpnSnapshot vpnSnapShot =
                 verifySetupPlatformVpn(
@@ -1925,14 +2475,19 @@
 
         // Mock network loss and verify a cleanup task is scheduled
         vpnSnapShot.nwCb.onLost(TEST_NETWORK);
-        verify(mExecutor).schedule(any(Runnable.class), anyLong(), any());
+        verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any());
 
         // Mock new network comes up and the cleanup task is cancelled
         vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
-        verify(mScheduledFuture).cancel(anyBoolean());
+        verify(mIkeSessionWrapper, never()).setNetwork(any(), anyInt(), anyInt(), anyInt());
 
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK_2,
+                new NetworkCapabilities.Builder().build());
         // Verify MOBIKE is triggered
-        verify(mIkeSessionWrapper).setNetwork(TEST_NETWORK_2);
+        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).setNetwork(eq(TEST_NETWORK_2),
+                eq(ESP_IP_VERSION_AUTO) /* ipVersion */,
+                eq(ESP_ENCAP_TYPE_AUTO) /* encapType */,
+                eq(DEFAULT_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT) /* keepaliveDelay */);
 
         // Mock the MOBIKE procedure
         vpnSnapShot.ikeCb.onIkeSessionConnectionInfoChanged(createIkeConnectInfo_2());
@@ -2022,9 +2577,13 @@
         // Mock network switch
         vpnSnapShot.nwCb.onLost(TEST_NETWORK);
         vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
+        // The old IKE Session will not be killed until receiving network capabilities change.
+        verify(mIkeSessionWrapper, never()).kill();
 
+        vpnSnapShot.nwCb.onCapabilitiesChanged(
+                TEST_NETWORK_2, new NetworkCapabilities.Builder().build());
         // Verify the old IKE Session is killed
-        verify(mIkeSessionWrapper).kill();
+        verify(mIkeSessionWrapper, timeout(TEST_TIMEOUT_MS)).kill();
 
         // Capture callbacks of the new IKE Session
         final Pair<IkeSessionCallback, ChildSessionCallback> cbPair =
@@ -2052,23 +2611,95 @@
         vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
     }
 
+    private String getDump(@NonNull final Vpn vpn) {
+        final StringWriter sw = new StringWriter();
+        final IndentingPrintWriter writer = new IndentingPrintWriter(sw, "");
+        vpn.dump(writer);
+        writer.flush();
+        return sw.toString();
+    }
+
+    private int countMatches(@NonNull final Pattern regexp, @NonNull final String string) {
+        final Matcher m = regexp.matcher(string);
+        int i = 0;
+        while (m.find()) ++i;
+        return i;
+    }
+
+    @Test
+    public void testNCEventChanges() throws Exception {
+        final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                .setLinkDownstreamBandwidthKbps(1000)
+                .setLinkUpstreamBandwidthKbps(500);
+
+        final Ikev2VpnProfile ikeProfile =
+                new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY)
+                        .setAuthPsk(TEST_VPN_PSK)
+                        .setBypassable(true /* isBypassable */)
+                        .setAutomaticNattKeepaliveTimerEnabled(true)
+                        .setAutomaticIpVersionSelectionEnabled(true)
+                        .build();
+
+        final PlatformVpnSnapshot vpnSnapShot =
+                verifySetupPlatformVpn(ikeProfile.toVpnProfile(),
+                        createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */),
+                        ncBuilder.build(), false /* mtuSupportsIpv6 */,
+                        true /* areLongLivedTcpConnectionsExpensive */);
+
+        // Calls to onCapabilitiesChanged will be thrown to the executor for execution ; by
+        // default this will incur a 10ms delay before it's executed, messing with the timing
+        // of the log and having the checks for counts in equals() below flake.
+        mExecutor.executeDirect = true;
+
+        // First nc changed triggered by verifySetupPlatformVpn
+        final Pattern pattern = Pattern.compile("Cap changed from", Pattern.MULTILINE);
+        final String stage1 = getDump(vpnSnapShot.vpn);
+        assertEquals(1, countMatches(pattern, stage1));
+
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build());
+        final String stage2 = getDump(vpnSnapShot.vpn);
+        // Was the same caps, there should still be only 1 match
+        assertEquals(1, countMatches(pattern, stage2));
+
+        ncBuilder.setLinkDownstreamBandwidthKbps(1200)
+                .setLinkUpstreamBandwidthKbps(300);
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build());
+        final String stage3 = getDump(vpnSnapShot.vpn);
+        // Was not an important change, should not be logged, still only 1 match
+        assertEquals(1, countMatches(pattern, stage3));
+
+        ncBuilder.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build());
+        final String stage4 = getDump(vpnSnapShot.vpn);
+        // Change to caps is important, should cause a new match
+        assertEquals(2, countMatches(pattern, stage4));
+
+        ncBuilder.removeCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+        ncBuilder.setLinkDownstreamBandwidthKbps(600);
+        vpnSnapShot.nwCb.onCapabilitiesChanged(TEST_NETWORK, ncBuilder.build());
+        final String stage5 = getDump(vpnSnapShot.vpn);
+        // Change to caps is important, should cause a new match even with the unimportant change
+        assertEquals(3, countMatches(pattern, stage5));
+    }
+    // TODO : beef up event logs tests
+
     private void verifyHandlingNetworkLoss(PlatformVpnSnapshot vpnSnapShot) throws Exception {
         // Forget the #sendLinkProperties during first setup.
         reset(mMockNetworkAgent);
 
-        final ArgumentCaptor<Runnable> runnableCaptor =
-                ArgumentCaptor.forClass(Runnable.class);
-
         // Mock network loss
         vpnSnapShot.nwCb.onLost(TEST_NETWORK);
 
         // Mock the grace period expires
-        verify(mExecutor).schedule(runnableCaptor.capture(), anyLong(), any());
-        runnableCaptor.getValue().run();
+        verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any());
 
         final ArgumentCaptor<LinkProperties> lpCaptor =
                 ArgumentCaptor.forClass(LinkProperties.class);
-        verify(mMockNetworkAgent).doSendLinkProperties(lpCaptor.capture());
+        verify(mMockNetworkAgent, timeout(TEST_TIMEOUT_MS))
+                .doSendLinkProperties(lpCaptor.capture());
         final LinkProperties lp = lpCaptor.getValue();
 
         assertNull(lp.getInterfaceName());
@@ -2112,7 +2743,8 @@
 
     private void verifyMobikeTriggered(List<Network> expected) {
         final ArgumentCaptor<Network> networkCaptor = ArgumentCaptor.forClass(Network.class);
-        verify(mIkeSessionWrapper).setNetwork(networkCaptor.capture());
+        verify(mIkeSessionWrapper).setNetwork(networkCaptor.capture(),
+                anyInt() /* ipVersion */, anyInt() /* encapType */, anyInt() /* keepaliveDelay */);
         assertEquals(expected, Collections.singletonList(networkCaptor.getValue()));
     }
 
@@ -2128,7 +2760,8 @@
         connectivityDiagCallback.onDataStallSuspected(report);
 
         // Should not trigger MOBIKE if MOBIKE is not enabled
-        verify(mIkeSessionWrapper, never()).setNetwork(any());
+        verify(mIkeSessionWrapper, never()).setNetwork(any() /* network */,
+                anyInt() /* ipVersion */, anyInt() /* encapType */, anyInt() /* keepaliveDelay */);
     }
 
     @Test
@@ -2148,7 +2781,8 @@
         // Expect to skip other data stall event if MOBIKE was started.
         reset(mIkeSessionWrapper);
         connectivityDiagCallback.onDataStallSuspected(report);
-        verify(mIkeSessionWrapper, never()).setNetwork(any());
+        verify(mIkeSessionWrapper, never()).setNetwork(any() /* network */,
+                anyInt() /* ipVersion */, anyInt() /* encapType */, anyInt() /* keepaliveDelay */);
 
         reset(mIkev2SessionCreator);
 
@@ -2163,9 +2797,7 @@
         // variables(timer counter and boolean) was reset.
         ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
                 NetworkAgent.VALIDATION_STATUS_NOT_VALID);
-        final ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
-        verify(mExecutor).schedule(runnableCaptor.capture(), anyLong(), any());
-        runnableCaptor.getValue().run();
+        verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any());
         verify(mIkev2SessionCreator, never()).createIkeSession(
                 any(), any(), any(), any(), any(), any());
     }
@@ -2191,17 +2823,16 @@
                 NetworkAgent.VALIDATION_STATUS_NOT_VALID);
 
         // Verify reset is scheduled and run.
-        final ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
-        verify(mExecutor).schedule(runnableCaptor.capture(), anyLong(), any());
+        verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any());
 
         // Another invalid status reported should not trigger other scheduled recovery.
         reset(mExecutor);
         ((Vpn.IkeV2VpnRunner) vpnSnapShot.vpn.mVpnRunner).onValidationStatus(
                 NetworkAgent.VALIDATION_STATUS_NOT_VALID);
-        verify(mExecutor, never()).schedule(runnableCaptor.capture(), anyLong(), any());
+        verify(mExecutor, never()).schedule(any(Runnable.class), anyLong(), any());
 
-        runnableCaptor.getValue().run();
-        verify(mIkev2SessionCreator).createIkeSession(any(), any(), any(), any(), any(), any());
+        verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS))
+                .createIkeSession(any(), any(), any(), any(), any(), any());
     }
 
     @Test
@@ -2473,15 +3104,23 @@
         }
 
         @Override
-        public long getNextRetryDelaySeconds(int retryCount) {
+        public long getNextRetryDelayMs(int retryCount) {
             // Simply return retryCount as the delay seconds for retrying.
-            return retryCount;
+            return retryCount * 1000;
         }
 
         @Override
         public ScheduledThreadPoolExecutor newScheduledThreadPoolExecutor() {
             return mExecutor;
         }
+
+        public boolean mIgnoreCallingUidChecks = true;
+        @Override
+        public void verifyCallingUidAndPackage(Context context, String packageName, int userId) {
+            if (!mIgnoreCallingUidChecks) {
+                super.verifyCallingUidAndPackage(context, packageName, userId);
+            }
+        }
     }
 
     /**
@@ -2544,30 +3183,4 @@
         } catch (Exception e) {
         }
     }
-
-    private void setMockedNetworks(final Map<Network, NetworkCapabilities> networks) {
-        doAnswer(invocation -> {
-            final Network network = (Network) invocation.getArguments()[0];
-            return networks.get(network);
-        }).when(mConnectivityManager).getNetworkCapabilities(any());
-    }
-
-    // Need multiple copies of this, but Java's Stream objects can't be reused or
-    // duplicated.
-    private Stream<String> publicIpV4Routes() {
-        return Stream.of(
-                "0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4",
-                "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6",
-                "172.0.0.0/12", "172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9",
-                "173.0.0.0/8", "174.0.0.0/7", "176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11",
-                "192.160.0.0/13", "192.169.0.0/16", "192.170.0.0/15", "192.172.0.0/14",
-                "192.176.0.0/12", "192.192.0.0/10", "193.0.0.0/8", "194.0.0.0/7",
-                "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4");
-    }
-
-    private Stream<String> publicIpV6Routes() {
-        return Stream.of(
-                "::/1", "8000::/2", "c000::/3", "e000::/4", "f000::/5", "f800::/6",
-                "fe00::/8", "2605:ef80:e:af1d::/64");
-    }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
new file mode 100644
index 0000000..d9acc61
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
@@ -0,0 +1,327 @@
+/*
+ * 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 com.android.server.connectivity.mdns
+
+import android.net.InetAddresses.parseNumericAddress
+import android.net.LinkAddress
+import android.net.Network
+import android.net.nsd.NsdServiceInfo
+import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
+import com.android.net.module.util.SharedLog
+import com.android.server.connectivity.mdns.MdnsAdvertiser.AdvertiserCallback
+import com.android.server.connectivity.mdns.MdnsSocketProvider.SocketCallback
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.waitForIdle
+import java.net.NetworkInterface
+import java.util.Objects
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+private const val SERVICE_ID_1 = 1
+private const val SERVICE_ID_2 = 2
+private const val LONG_SERVICE_ID_1 = 3
+private const val LONG_SERVICE_ID_2 = 4
+private const val CASE_INSENSITIVE_TEST_SERVICE_ID = 5
+private const val TIMEOUT_MS = 10_000L
+private val TEST_ADDR = parseNumericAddress("2001:db8::123")
+private val TEST_LINKADDR = LinkAddress(TEST_ADDR, 64 /* prefixLength */)
+private val TEST_NETWORK_1 = mock(Network::class.java)
+private val TEST_NETWORK_2 = mock(Network::class.java)
+private val TEST_HOSTNAME = arrayOf("Android_test", "local")
+private const val TEST_SUBTYPE = "_subtype"
+
+private val SERVICE_1 = NsdServiceInfo("TestServiceName", "_advertisertest._tcp").apply {
+    port = 12345
+    hostAddresses = listOf(TEST_ADDR)
+    network = TEST_NETWORK_1
+}
+
+private val LONG_SERVICE_1 =
+    NsdServiceInfo("a".repeat(48) + "TestServiceName", "_longadvertisertest._tcp").apply {
+    port = 12345
+    hostAddresses = listOf(TEST_ADDR)
+    network = TEST_NETWORK_1
+    }
+
+private val ALL_NETWORKS_SERVICE = NsdServiceInfo("TestServiceName", "_advertisertest._tcp").apply {
+    port = 12345
+    hostAddresses = listOf(TEST_ADDR)
+    network = null
+}
+
+private val ALL_NETWORKS_SERVICE_2 =
+    NsdServiceInfo("TESTSERVICENAME", "_ADVERTISERTEST._tcp").apply {
+        port = 12345
+        hostAddresses = listOf(TEST_ADDR)
+        network = null
+    }
+
+private val LONG_ALL_NETWORKS_SERVICE =
+    NsdServiceInfo("a".repeat(48) + "TestServiceName", "_longadvertisertest._tcp").apply {
+        port = 12345
+        hostAddresses = listOf(TEST_ADDR)
+        network = null
+    }
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class MdnsAdvertiserTest {
+    private val thread = HandlerThread(MdnsAdvertiserTest::class.simpleName)
+    private val handler by lazy { Handler(thread.looper) }
+    private val socketProvider = mock(MdnsSocketProvider::class.java)
+    private val cb = mock(AdvertiserCallback::class.java)
+    private val sharedlog = mock(SharedLog::class.java)
+
+    private val mockSocket1 = mock(MdnsInterfaceSocket::class.java)
+    private val mockSocket2 = mock(MdnsInterfaceSocket::class.java)
+    private val mockInterfaceAdvertiser1 = mock(MdnsInterfaceAdvertiser::class.java)
+    private val mockInterfaceAdvertiser2 = mock(MdnsInterfaceAdvertiser::class.java)
+    private val mockDeps = mock(MdnsAdvertiser.Dependencies::class.java)
+
+    @Before
+    fun setUp() {
+        thread.start()
+        doReturn(TEST_HOSTNAME).`when`(mockDeps).generateHostname()
+        doReturn(mockInterfaceAdvertiser1).`when`(mockDeps).makeAdvertiser(eq(mockSocket1),
+                any(), any(), any(), any(), eq(TEST_HOSTNAME), any()
+        )
+        doReturn(mockInterfaceAdvertiser2).`when`(mockDeps).makeAdvertiser(eq(mockSocket2),
+                any(), any(), any(), any(), eq(TEST_HOSTNAME), any()
+        )
+        doReturn(true).`when`(mockInterfaceAdvertiser1).isProbing(anyInt())
+        doReturn(true).`when`(mockInterfaceAdvertiser2).isProbing(anyInt())
+        doReturn(createEmptyNetworkInterface()).`when`(mockSocket1).getInterface()
+        doReturn(createEmptyNetworkInterface()).`when`(mockSocket2).getInterface()
+    }
+
+    @After
+    fun tearDown() {
+        thread.quitSafely()
+        thread.join()
+    }
+
+    private fun createEmptyNetworkInterface(): NetworkInterface {
+        val constructor = NetworkInterface::class.java.getDeclaredConstructor()
+        constructor.isAccessible = true
+        return constructor.newInstance()
+    }
+
+    @Test
+    fun testAddService_OneNetwork() {
+        val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog)
+        postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1, null /* subtype */) }
+
+        val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
+        verify(socketProvider).requestSocket(eq(TEST_NETWORK_1), socketCbCaptor.capture())
+
+        val socketCb = socketCbCaptor.value
+        postSync { socketCb.onSocketCreated(TEST_NETWORK_1, mockSocket1, listOf(TEST_LINKADDR)) }
+
+        val intAdvCbCaptor = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
+        verify(mockDeps).makeAdvertiser(
+            eq(mockSocket1),
+            eq(listOf(TEST_LINKADDR)),
+            eq(thread.looper),
+            any(),
+            intAdvCbCaptor.capture(),
+            eq(TEST_HOSTNAME),
+            any()
+        )
+
+        doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
+        postSync { intAdvCbCaptor.value.onRegisterServiceSucceeded(
+                mockInterfaceAdvertiser1, SERVICE_ID_1) }
+        verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1), argThat { it.matches(SERVICE_1) })
+
+        postSync { socketCb.onInterfaceDestroyed(TEST_NETWORK_1, mockSocket1) }
+        verify(mockInterfaceAdvertiser1).destroyNow()
+    }
+
+    @Test
+    fun testAddService_AllNetworks() {
+        val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog)
+        postSync { advertiser.addService(SERVICE_ID_1, ALL_NETWORKS_SERVICE, TEST_SUBTYPE) }
+
+        val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
+        verify(socketProvider).requestSocket(eq(ALL_NETWORKS_SERVICE.network),
+                socketCbCaptor.capture())
+
+        val socketCb = socketCbCaptor.value
+        postSync { socketCb.onSocketCreated(TEST_NETWORK_1, mockSocket1, listOf(TEST_LINKADDR)) }
+        postSync { socketCb.onSocketCreated(TEST_NETWORK_2, mockSocket2, listOf(TEST_LINKADDR)) }
+
+        val intAdvCbCaptor1 = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
+        val intAdvCbCaptor2 = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
+        verify(mockDeps).makeAdvertiser(eq(mockSocket1), eq(listOf(TEST_LINKADDR)),
+                eq(thread.looper), any(), intAdvCbCaptor1.capture(), eq(TEST_HOSTNAME), any()
+        )
+        verify(mockDeps).makeAdvertiser(eq(mockSocket2), eq(listOf(TEST_LINKADDR)),
+                eq(thread.looper), any(), intAdvCbCaptor2.capture(), eq(TEST_HOSTNAME), any()
+        )
+        verify(mockInterfaceAdvertiser1).addService(
+                anyInt(), eq(ALL_NETWORKS_SERVICE), eq(TEST_SUBTYPE))
+        verify(mockInterfaceAdvertiser2).addService(
+                anyInt(), eq(ALL_NETWORKS_SERVICE), eq(TEST_SUBTYPE))
+
+        doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
+        postSync { intAdvCbCaptor1.value.onRegisterServiceSucceeded(
+                mockInterfaceAdvertiser1, SERVICE_ID_1) }
+
+        // Need both advertisers to finish probing and call onRegisterServiceSucceeded
+        verify(cb, never()).onRegisterServiceSucceeded(anyInt(), any())
+        doReturn(false).`when`(mockInterfaceAdvertiser2).isProbing(SERVICE_ID_1)
+        postSync { intAdvCbCaptor2.value.onRegisterServiceSucceeded(
+                mockInterfaceAdvertiser2, SERVICE_ID_1) }
+        verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1),
+                argThat { it.matches(ALL_NETWORKS_SERVICE) })
+
+        // Unregister the service
+        postSync { advertiser.removeService(SERVICE_ID_1) }
+        verify(mockInterfaceAdvertiser1).removeService(SERVICE_ID_1)
+        verify(mockInterfaceAdvertiser2).removeService(SERVICE_ID_1)
+
+        // Interface advertisers call onDestroyed after sending exit announcements
+        postSync { intAdvCbCaptor1.value.onDestroyed(mockSocket1) }
+        verify(socketProvider, never()).unrequestSocket(any())
+        postSync { intAdvCbCaptor2.value.onDestroyed(mockSocket2) }
+        verify(socketProvider).unrequestSocket(socketCb)
+    }
+
+    @Test
+    fun testAddService_Conflicts() {
+        val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog)
+        postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1, null /* subtype */) }
+
+        val oneNetSocketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
+        verify(socketProvider).requestSocket(eq(TEST_NETWORK_1), oneNetSocketCbCaptor.capture())
+        val oneNetSocketCb = oneNetSocketCbCaptor.value
+
+        // Register a service with the same name on all networks (name conflict)
+        postSync { advertiser.addService(SERVICE_ID_2, ALL_NETWORKS_SERVICE, null /* subtype */) }
+        val allNetSocketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
+        verify(socketProvider).requestSocket(eq(null), allNetSocketCbCaptor.capture())
+        val allNetSocketCb = allNetSocketCbCaptor.value
+
+        postSync { advertiser.addService(LONG_SERVICE_ID_1, LONG_SERVICE_1, null /* subtype */) }
+        postSync { advertiser.addService(LONG_SERVICE_ID_2, LONG_ALL_NETWORKS_SERVICE,
+                null /* subtype */) }
+
+        postSync { advertiser.addService(CASE_INSENSITIVE_TEST_SERVICE_ID, ALL_NETWORKS_SERVICE_2,
+                null /* subtype */) }
+
+        // Callbacks for matching network and all networks both get the socket
+        postSync {
+            oneNetSocketCb.onSocketCreated(TEST_NETWORK_1, mockSocket1, listOf(TEST_LINKADDR))
+            allNetSocketCb.onSocketCreated(TEST_NETWORK_1, mockSocket1, listOf(TEST_LINKADDR))
+        }
+
+        val expectedRenamed = NsdServiceInfo(
+                "${ALL_NETWORKS_SERVICE.serviceName} (2)", ALL_NETWORKS_SERVICE.serviceType).apply {
+            port = ALL_NETWORKS_SERVICE.port
+            hostAddresses = ALL_NETWORKS_SERVICE.hostAddresses
+            network = ALL_NETWORKS_SERVICE.network
+        }
+
+        val expectedLongRenamed = NsdServiceInfo(
+            "${LONG_ALL_NETWORKS_SERVICE.serviceName.dropLast(4)} (2)",
+            LONG_ALL_NETWORKS_SERVICE.serviceType).apply {
+            port = LONG_ALL_NETWORKS_SERVICE.port
+            hostAddresses = LONG_ALL_NETWORKS_SERVICE.hostAddresses
+            network = LONG_ALL_NETWORKS_SERVICE.network
+        }
+
+        val expectedCaseInsensitiveRenamed = NsdServiceInfo(
+            "${ALL_NETWORKS_SERVICE_2.serviceName} (3)", ALL_NETWORKS_SERVICE_2.serviceType
+        ).apply {
+            port = ALL_NETWORKS_SERVICE_2.port
+            hostAddresses = ALL_NETWORKS_SERVICE_2.hostAddresses
+            network = ALL_NETWORKS_SERVICE_2.network
+        }
+
+        val intAdvCbCaptor = ArgumentCaptor.forClass(MdnsInterfaceAdvertiser.Callback::class.java)
+        verify(mockDeps).makeAdvertiser(eq(mockSocket1), eq(listOf(TEST_LINKADDR)),
+                eq(thread.looper), any(), intAdvCbCaptor.capture(), eq(TEST_HOSTNAME), any()
+        )
+        verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1),
+                argThat { it.matches(SERVICE_1) }, eq(null))
+        verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_2),
+                argThat { it.matches(expectedRenamed) }, eq(null))
+        verify(mockInterfaceAdvertiser1).addService(eq(LONG_SERVICE_ID_1),
+                argThat { it.matches(LONG_SERVICE_1) }, eq(null))
+        verify(mockInterfaceAdvertiser1).addService(eq(LONG_SERVICE_ID_2),
+            argThat { it.matches(expectedLongRenamed) }, eq(null))
+        verify(mockInterfaceAdvertiser1).addService(eq(CASE_INSENSITIVE_TEST_SERVICE_ID),
+            argThat { it.matches(expectedCaseInsensitiveRenamed) }, eq(null))
+
+        doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
+        postSync { intAdvCbCaptor.value.onRegisterServiceSucceeded(
+                mockInterfaceAdvertiser1, SERVICE_ID_1) }
+        verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1), argThat { it.matches(SERVICE_1) })
+
+        doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_2)
+        postSync { intAdvCbCaptor.value.onRegisterServiceSucceeded(
+                mockInterfaceAdvertiser1, SERVICE_ID_2) }
+        verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_2),
+                argThat { it.matches(expectedRenamed) })
+
+        postSync { oneNetSocketCb.onInterfaceDestroyed(TEST_NETWORK_1, mockSocket1) }
+        postSync { allNetSocketCb.onInterfaceDestroyed(TEST_NETWORK_1, mockSocket1) }
+
+        // destroyNow can be called multiple times
+        verify(mockInterfaceAdvertiser1, atLeastOnce()).destroyNow()
+    }
+
+    @Test
+    fun testRemoveService_whenAllServiceRemoved_thenUpdateHostName() {
+        val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog)
+        verify(mockDeps, times(1)).generateHostname()
+        postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1, null /* subtype */) }
+        postSync { advertiser.removeService(SERVICE_ID_1) }
+        verify(mockDeps, times(2)).generateHostname()
+    }
+
+    private fun postSync(r: () -> Unit) {
+        handler.post(r)
+        handler.waitForIdle(TIMEOUT_MS)
+    }
+}
+
+// NsdServiceInfo does not implement equals; this is useful to use in argument matchers
+private fun NsdServiceInfo.matches(other: NsdServiceInfo): Boolean {
+    return Objects.equals(serviceName, other.serviceName) &&
+            Objects.equals(serviceType, other.serviceType) &&
+            Objects.equals(attributes, other.attributes) &&
+            Objects.equals(hostAddresses, other.hostAddresses) &&
+            port == other.port &&
+            Objects.equals(network, other.network)
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
new file mode 100644
index 0000000..7c6cb3e
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAnnouncerTest.kt
@@ -0,0 +1,273 @@
+/*
+ * 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 com.android.server.connectivity.mdns
+
+import android.net.InetAddresses.parseNumericAddress
+import android.os.Build
+import android.os.HandlerThread
+import android.os.SystemClock
+import com.android.internal.util.HexDump
+import com.android.server.connectivity.mdns.MdnsAnnouncer.AnnouncementInfo
+import com.android.server.connectivity.mdns.MdnsAnnouncer.BaseAnnouncementInfo
+import com.android.server.connectivity.mdns.MdnsRecordRepository.getReverseDnsAddress
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import java.net.DatagramPacket
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.any
+import org.mockito.Mockito.atLeast
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
+
+private const val FIRST_ANNOUNCES_DELAY = 100L
+private const val FIRST_ANNOUNCES_COUNT = 2
+private const val NEXT_ANNOUNCES_DELAY = 1L
+private const val TEST_TIMEOUT_MS = 1000L
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class MdnsAnnouncerTest {
+
+    private val thread = HandlerThread(MdnsAnnouncerTest::class.simpleName)
+    private val socket = mock(MdnsInterfaceSocket::class.java)
+    private val buffer = ByteArray(1500)
+
+    @Before
+    fun setUp() {
+        doReturn(true).`when`(socket).hasJoinedIpv6()
+        thread.start()
+    }
+
+    @After
+    fun tearDown() {
+        thread.quitSafely()
+        thread.join()
+    }
+
+    private class TestAnnouncementInfo(
+        announcedRecords: List<MdnsRecord>,
+        additionalRecords: List<MdnsRecord>
+    ) : AnnouncementInfo(1 /* serviceId */, announcedRecords, additionalRecords) {
+        override fun getDelayMs(nextIndex: Int) =
+                if (nextIndex < FIRST_ANNOUNCES_COUNT) {
+                    FIRST_ANNOUNCES_DELAY
+                } else {
+                    NEXT_ANNOUNCES_DELAY
+                }
+    }
+
+    @Test
+    fun testAnnounce() {
+        val replySender = MdnsReplySender("testiface", thread.looper, socket, buffer)
+        @Suppress("UNCHECKED_CAST")
+        val cb = mock(MdnsPacketRepeater.PacketRepeaterCallback::class.java)
+                as MdnsPacketRepeater.PacketRepeaterCallback<BaseAnnouncementInfo>
+        val announcer = MdnsAnnouncer("testiface", thread.looper, replySender, cb)
+        /*
+        The expected packet replicates records announced when registering a service, as observed in
+        the legacy mDNS implementation (some ordering differs to be more readable).
+        Obtained with scapy 2.5.0 RC3 (2.4.5 does not compress TLDs like .arpa properly) with:
+        scapy.raw(scapy.dns_compress(scapy.DNS(rd=0, qr=1, aa=1,
+        qd = None,
+        an =
+        scapy.DNSRR(type='PTR', rrname='123.2.0.192.in-addr.arpa.', rdata='Android.local',
+            rclass=0x8001, ttl=120) /
+        scapy.DNSRR(type='PTR',
+            rrname='3.2.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.B.D.0.1.0.0.2.ip6.arpa',
+            rdata='Android.local', rclass=0x8001, ttl=120) /
+        scapy.DNSRR(type='PTR',
+            rrname='6.5.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.B.D.0.1.0.0.2.ip6.arpa',
+            rdata='Android.local', rclass=0x8001, ttl=120) /
+        scapy.DNSRR(type='PTR', rrname='_testtype._tcp.local',
+            rdata='testservice._testtype._tcp.local', rclass='IN', ttl=4500) /
+	    scapy.DNSRRSRV(rrname='testservice._testtype._tcp.local', rclass=0x8001, port=31234,
+	        target='Android.local', ttl=120) /
+	    scapy.DNSRR(type='TXT', rrname='testservice._testtype._tcp.local', rclass=0x8001, rdata='',
+	        ttl=4500) /
+        scapy.DNSRR(type='A', rrname='Android.local', rclass=0x8001, rdata='192.0.2.123', ttl=120) /
+        scapy.DNSRR(type='AAAA', rrname='Android.local', rclass=0x8001, rdata='2001:db8::123',
+            ttl=120) /
+        scapy.DNSRR(type='AAAA', rrname='Android.local', rclass=0x8001, rdata='2001:db8::456',
+            ttl=120),
+        ar =
+        scapy.DNSRRNSEC(rrname='123.2.0.192.in-addr.arpa.', rclass=0x8001, ttl=120,
+            nextname='123.2.0.192.in-addr.arpa.', typebitmaps=[12]) /
+        scapy.DNSRRNSEC(
+            rrname='3.2.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.B.D.0.1.0.0.2.ip6.arpa',
+            rclass=0x8001, ttl=120,
+            nextname='3.2.1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.B.D.0.1.0.0.2.ip6.arpa',
+            typebitmaps=[12]) /
+        scapy.DNSRRNSEC(
+            rrname='6.5.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.B.D.0.1.0.0.2.ip6.arpa',
+            rclass=0x8001, ttl=120,
+            nextname='6.5.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.B.D.0.1.0.0.2.ip6.arpa',
+            typebitmaps=[12]) /
+	    scapy.DNSRRNSEC(
+	        rrname='testservice._testtype._tcp.local', rclass=0x8001, ttl=4500,
+	        nextname='testservice._testtype._tcp.local', typebitmaps=[16, 33]) /
+        scapy.DNSRRNSEC(
+            rrname='Android.local', rclass=0x8001, ttl=120, nextname='Android.local',
+            typebitmaps=[1, 28]))
+        )).hex().upper()
+        */
+        val expected = "00008400000000090000000503313233013201300331393207696E2D61646472046172706" +
+                "100000C800100000078000F07416E64726F6964056C6F63616C00013301320131013001300130013" +
+                "00130013001300130013001300130013001300130013001300130013001300130013001380142014" +
+                "40130013101300130013203697036C020000C8001000000780002C030013601350134C045000C800" +
+                "1000000780002C030095F7465737474797065045F746370C038000C000100001194000E0B7465737" +
+                "473657276696365C0A5C0C000218001000000780008000000007A02C030C0C000108001000011940" +
+                "000C03000018001000000780004C000027BC030001C800100000078001020010DB80000000000000" +
+                "00000000123C030001C800100000078001020010DB8000000000000000000000456C00C002F80010" +
+                "00000780006C00C00020008C03F002F8001000000780006C03F00020008C091002F8001000000780" +
+                "006C09100020008C0C0002F8001000011940009C0C000050000800040C030002F800100000078000" +
+                "8C030000440000008"
+
+        val hostname = arrayOf("Android", "local")
+        val serviceType = arrayOf("_testtype", "_tcp", "local")
+        val serviceName = arrayOf("testservice", "_testtype", "_tcp", "local")
+        val v4Addr = parseNumericAddress("192.0.2.123")
+        val v6Addr1 = parseNumericAddress("2001:DB8::123")
+        val v6Addr2 = parseNumericAddress("2001:DB8::456")
+        val v4AddrRev = getReverseDnsAddress(v4Addr)
+        val v6Addr1Rev = getReverseDnsAddress(v6Addr1)
+        val v6Addr2Rev = getReverseDnsAddress(v6Addr2)
+
+        val announcedRecords = listOf(
+                // Reverse address records
+                MdnsPointerRecord(v4AddrRev,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        hostname),
+                MdnsPointerRecord(v6Addr1Rev,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        hostname),
+                MdnsPointerRecord(v6Addr2Rev,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        hostname),
+                // Service registration records (RFC6763)
+                MdnsPointerRecord(
+                        serviceType,
+                        0L /* receiptTimeMillis */,
+                        // Not a unique name owned by the announcer, so cacheFlush=false
+                        false /* cacheFlush */,
+                        4500000L /* ttlMillis */,
+                        serviceName),
+                MdnsServiceRecord(
+                        serviceName,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        0 /* servicePriority */,
+                        0 /* serviceWeight */,
+                        31234 /* servicePort */,
+                        hostname),
+                MdnsTextRecord(
+                        serviceName,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        4500000L /* ttlMillis */,
+                        emptyList() /* entries */),
+                // Address records for the hostname
+                MdnsInetAddressRecord(hostname,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        v4Addr),
+                MdnsInetAddressRecord(hostname,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        v6Addr1),
+                MdnsInetAddressRecord(hostname,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        v6Addr2))
+        // Negative responses (RFC6762 6.1)
+        val additionalRecords = listOf(
+                MdnsNsecRecord(v4AddrRev,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        v4AddrRev,
+                        intArrayOf(MdnsRecord.TYPE_PTR)),
+                MdnsNsecRecord(v6Addr1Rev,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        v6Addr1Rev,
+                        intArrayOf(MdnsRecord.TYPE_PTR)),
+                MdnsNsecRecord(v6Addr2Rev,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        v6Addr2Rev,
+                        intArrayOf(MdnsRecord.TYPE_PTR)),
+                MdnsNsecRecord(serviceName,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        4500000L /* ttlMillis */,
+                        serviceName,
+                        intArrayOf(MdnsRecord.TYPE_TXT, MdnsRecord.TYPE_SRV)),
+                MdnsNsecRecord(hostname,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        hostname,
+                        intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)))
+        val request = TestAnnouncementInfo(announcedRecords, additionalRecords)
+
+        val timeStart = SystemClock.elapsedRealtime()
+        val startDelay = 50L
+        val sendId = 1
+        announcer.startSending(sendId, request, startDelay)
+
+        val captor = ArgumentCaptor.forClass(DatagramPacket::class.java)
+        repeat(FIRST_ANNOUNCES_COUNT) { i ->
+            verify(cb, timeout(TEST_TIMEOUT_MS)).onSent(i, request)
+            verify(socket, atLeast(i + 1)).send(any())
+            val now = SystemClock.elapsedRealtime()
+            assertTrue(now > timeStart + startDelay + i * FIRST_ANNOUNCES_DELAY)
+            // Loops can be much slower than the expected timing (>100ms delay), use
+            // TEST_TIMEOUT_MS as tolerance.
+            assertTrue(now < timeStart + startDelay + (i + 1) * FIRST_ANNOUNCES_DELAY +
+                TEST_TIMEOUT_MS)
+        }
+
+        // Subsequent announces should happen quickly (NEXT_ANNOUNCES_DELAY)
+        verify(socket, timeout(TEST_TIMEOUT_MS).times(MdnsAnnouncer.ANNOUNCEMENT_COUNT))
+                .send(captor.capture())
+        verify(cb, timeout(TEST_TIMEOUT_MS)).onFinished(request)
+
+        captor.allValues.forEach {
+            assertEquals(expected, HexDump.toHexString(it.data))
+        }
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
index 3e3c3bf..aeaca06 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
@@ -18,118 +18,296 @@
 
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 
-import static org.mockito.Mockito.mock;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+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.net.Network;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.text.TextUtils;
+import android.util.Pair;
 
+import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.mdns.MdnsSocketClientBase.SocketCreationCallback;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.HandlerUtils;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 
 /** Tests for {@link MdnsDiscoveryManager}. */
 @RunWith(DevSdkIgnoreRunner.class)
 @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
 public class MdnsDiscoveryManagerTests {
-
+    private static final long DEFAULT_TIMEOUT = 2000L;
     private static final String SERVICE_TYPE_1 = "_googlecast._tcp.local";
     private static final String SERVICE_TYPE_2 = "_test._tcp.local";
+    private static final Network NETWORK_1 = Mockito.mock(Network.class);
+    private static final Network NETWORK_2 = Mockito.mock(Network.class);
+    private static final Pair<String, Network> PER_NETWORK_SERVICE_TYPE_1_NULL_NETWORK =
+            Pair.create(SERVICE_TYPE_1, null);
+    private static final Pair<String, Network> PER_NETWORK_SERVICE_TYPE_1_NETWORK_1 =
+            Pair.create(SERVICE_TYPE_1, NETWORK_1);
+    private static final Pair<String, Network> PER_NETWORK_SERVICE_TYPE_2_NULL_NETWORK =
+            Pair.create(SERVICE_TYPE_2, null);
+    private static final Pair<String, Network> PER_NETWORK_SERVICE_TYPE_2_NETWORK_1 =
+            Pair.create(SERVICE_TYPE_2, NETWORK_1);
+    private static final Pair<String, Network> PER_NETWORK_SERVICE_TYPE_2_NETWORK_2 =
+            Pair.create(SERVICE_TYPE_2, NETWORK_2);
 
     @Mock private ExecutorProvider executorProvider;
-    @Mock private MdnsSocketClient socketClient;
-    @Mock private MdnsServiceTypeClient mockServiceTypeClientOne;
-    @Mock private MdnsServiceTypeClient mockServiceTypeClientTwo;
+    @Mock private MdnsSocketClientBase socketClient;
+    @Mock private MdnsServiceTypeClient mockServiceTypeClientType1NullNetwork;
+    @Mock private MdnsServiceTypeClient mockServiceTypeClientType1Network1;
+    @Mock private MdnsServiceTypeClient mockServiceTypeClientType2NullNetwork;
+    @Mock private MdnsServiceTypeClient mockServiceTypeClientType2Network1;
+    @Mock private MdnsServiceTypeClient mockServiceTypeClientType2Network2;
 
     @Mock MdnsServiceBrowserListener mockListenerOne;
     @Mock MdnsServiceBrowserListener mockListenerTwo;
+    @Mock SharedLog sharedLog;
     private MdnsDiscoveryManager discoveryManager;
+    private HandlerThread thread;
+    private Handler handler;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        when(mockServiceTypeClientOne.getServiceTypeLabels())
-                .thenReturn(TextUtils.split(SERVICE_TYPE_1, "\\."));
-        when(mockServiceTypeClientTwo.getServiceTypeLabels())
-                .thenReturn(TextUtils.split(SERVICE_TYPE_2, "\\."));
-
-        discoveryManager = new MdnsDiscoveryManager(executorProvider, socketClient) {
+        thread = new HandlerThread("MdnsDiscoveryManagerTests");
+        thread.start();
+        handler = new Handler(thread.getLooper());
+        doReturn(thread.getLooper()).when(socketClient).getLooper();
+        discoveryManager = new MdnsDiscoveryManager(executorProvider, socketClient,
+                sharedLog) {
                     @Override
-                    MdnsServiceTypeClient createServiceTypeClient(@NonNull String serviceType) {
-                        if (serviceType.equals(SERVICE_TYPE_1)) {
-                            return mockServiceTypeClientOne;
-                        } else if (serviceType.equals(SERVICE_TYPE_2)) {
-                            return mockServiceTypeClientTwo;
+                    MdnsServiceTypeClient createServiceTypeClient(@NonNull String serviceType,
+                            @Nullable Network network) {
+                        final Pair<String, Network> perNetworkServiceType =
+                                Pair.create(serviceType, network);
+                        if (perNetworkServiceType.equals(PER_NETWORK_SERVICE_TYPE_1_NULL_NETWORK)) {
+                            return mockServiceTypeClientType1NullNetwork;
+                        } else if (perNetworkServiceType.equals(
+                                PER_NETWORK_SERVICE_TYPE_1_NETWORK_1)) {
+                            return mockServiceTypeClientType1Network1;
+                        } else if (perNetworkServiceType.equals(
+                                PER_NETWORK_SERVICE_TYPE_2_NULL_NETWORK)) {
+                            return mockServiceTypeClientType2NullNetwork;
+                        } else if (perNetworkServiceType.equals(
+                                PER_NETWORK_SERVICE_TYPE_2_NETWORK_1)) {
+                            return mockServiceTypeClientType2Network1;
+                        } else if (perNetworkServiceType.equals(
+                                PER_NETWORK_SERVICE_TYPE_2_NETWORK_2)) {
+                            return mockServiceTypeClientType2Network2;
                         }
                         return null;
                     }
                 };
     }
 
+    @After
+    public void tearDown() {
+        if (thread != null) {
+            thread.quitSafely();
+        }
+    }
+
+    private void runOnHandler(Runnable r) {
+        handler.post(r);
+        HandlerUtils.waitForIdle(handler, DEFAULT_TIMEOUT);
+    }
+
+    private SocketCreationCallback expectSocketCreationCallback(String serviceType,
+            MdnsServiceBrowserListener listener, MdnsSearchOptions options) throws IOException {
+        final ArgumentCaptor<SocketCreationCallback> callbackCaptor =
+                ArgumentCaptor.forClass(SocketCreationCallback.class);
+        runOnHandler(() -> discoveryManager.registerListener(serviceType, listener, options));
+        verify(socketClient).startDiscovery();
+        verify(socketClient).notifyNetworkRequested(
+                eq(listener), eq(options.getNetwork()), callbackCaptor.capture());
+        return callbackCaptor.getValue();
+    }
+
     @Test
     public void registerListener_unregisterListener() throws IOException {
-        discoveryManager.registerListener(
-                SERVICE_TYPE_1, mockListenerOne, MdnsSearchOptions.getDefaultOptions());
-        verify(socketClient).startDiscovery();
-        verify(mockServiceTypeClientOne)
-                .startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
+        final MdnsSearchOptions options =
+                MdnsSearchOptions.newBuilder().setNetwork(null /* network */).build();
+        final SocketCreationCallback callback = expectSocketCreationCallback(
+                SERVICE_TYPE_1, mockListenerOne, options);
+        runOnHandler(() -> callback.onSocketCreated(null /* network */));
+        verify(mockServiceTypeClientType1NullNetwork).startSendAndReceive(mockListenerOne, options);
 
-        when(mockServiceTypeClientOne.stopSendAndReceive(mockListenerOne)).thenReturn(true);
-        discoveryManager.unregisterListener(SERVICE_TYPE_1, mockListenerOne);
-        verify(mockServiceTypeClientOne).stopSendAndReceive(mockListenerOne);
+        when(mockServiceTypeClientType1NullNetwork.stopSendAndReceive(mockListenerOne))
+                .thenReturn(true);
+        runOnHandler(() -> discoveryManager.unregisterListener(SERVICE_TYPE_1, mockListenerOne));
+        verify(mockServiceTypeClientType1NullNetwork).stopSendAndReceive(mockListenerOne);
         verify(socketClient).stopDiscovery();
     }
 
     @Test
     public void registerMultipleListeners() throws IOException {
-        discoveryManager.registerListener(
-                SERVICE_TYPE_1, mockListenerOne, MdnsSearchOptions.getDefaultOptions());
-        verify(socketClient).startDiscovery();
-        verify(mockServiceTypeClientOne)
-                .startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
+        final MdnsSearchOptions options =
+                MdnsSearchOptions.newBuilder().setNetwork(null /* network */).build();
+        final SocketCreationCallback callback = expectSocketCreationCallback(
+                SERVICE_TYPE_1, mockListenerOne, options);
+        runOnHandler(() -> callback.onSocketCreated(null /* network */));
+        verify(mockServiceTypeClientType1NullNetwork).startSendAndReceive(mockListenerOne, options);
+        runOnHandler(() -> callback.onSocketCreated(NETWORK_1));
+        verify(mockServiceTypeClientType1Network1).startSendAndReceive(mockListenerOne, options);
 
-        discoveryManager.registerListener(
-                SERVICE_TYPE_2, mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
-        verify(mockServiceTypeClientTwo)
-                .startSendAndReceive(mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
+        final SocketCreationCallback callback2 = expectSocketCreationCallback(
+                SERVICE_TYPE_2, mockListenerTwo, options);
+        runOnHandler(() -> callback2.onSocketCreated(null /* network */));
+        verify(mockServiceTypeClientType2NullNetwork).startSendAndReceive(mockListenerTwo, options);
+        runOnHandler(() -> callback2.onSocketCreated(NETWORK_2));
+        verify(mockServiceTypeClientType2Network2).startSendAndReceive(mockListenerTwo, options);
     }
 
     @Test
-    public void onResponseReceived() {
-        discoveryManager.registerListener(
-                SERVICE_TYPE_1, mockListenerOne, MdnsSearchOptions.getDefaultOptions());
-        discoveryManager.registerListener(
-                SERVICE_TYPE_2, mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
+    public void onResponseReceived() throws IOException {
+        final MdnsSearchOptions options1 =
+                MdnsSearchOptions.newBuilder().setNetwork(null /* network */).build();
+        final SocketCreationCallback callback = expectSocketCreationCallback(
+                SERVICE_TYPE_1, mockListenerOne, options1);
+        runOnHandler(() -> callback.onSocketCreated(null /* network */));
+        verify(mockServiceTypeClientType1NullNetwork).startSendAndReceive(
+                mockListenerOne, options1);
+        runOnHandler(() -> callback.onSocketCreated(NETWORK_1));
+        verify(mockServiceTypeClientType1Network1).startSendAndReceive(mockListenerOne, options1);
 
-        MdnsResponse responseForServiceTypeOne = createMockResponse(SERVICE_TYPE_1);
-        discoveryManager.onResponseReceived(responseForServiceTypeOne);
-        verify(mockServiceTypeClientOne).processResponse(responseForServiceTypeOne);
+        final MdnsSearchOptions options2 =
+                MdnsSearchOptions.newBuilder().setNetwork(NETWORK_2).build();
+        final SocketCreationCallback callback2 = expectSocketCreationCallback(
+                SERVICE_TYPE_2, mockListenerTwo, options2);
+        runOnHandler(() -> callback2.onSocketCreated(NETWORK_2));
+        verify(mockServiceTypeClientType2Network2).startSendAndReceive(mockListenerTwo, options2);
 
-        MdnsResponse responseForServiceTypeTwo = createMockResponse(SERVICE_TYPE_2);
-        discoveryManager.onResponseReceived(responseForServiceTypeTwo);
-        verify(mockServiceTypeClientTwo).processResponse(responseForServiceTypeTwo);
+        final MdnsPacket responseForServiceTypeOne = createMdnsPacket(SERVICE_TYPE_1);
+        final int ifIndex = 1;
+        runOnHandler(() -> discoveryManager.onResponseReceived(
+                responseForServiceTypeOne, ifIndex, null /* network */));
+        // Packets for network null are only processed by the ServiceTypeClient for network null
+        verify(mockServiceTypeClientType1NullNetwork).processResponse(responseForServiceTypeOne,
+                ifIndex, null /* network */);
+        verify(mockServiceTypeClientType1Network1, never()).processResponse(any(), anyInt(), any());
+        verify(mockServiceTypeClientType2Network2, never()).processResponse(any(), anyInt(), any());
 
-        MdnsResponse responseForSubtype = createMockResponse("subtype._sub._googlecast._tcp.local");
-        discoveryManager.onResponseReceived(responseForSubtype);
-        verify(mockServiceTypeClientOne).processResponse(responseForSubtype);
+        final MdnsPacket responseForServiceTypeTwo = createMdnsPacket(SERVICE_TYPE_2);
+        runOnHandler(() -> discoveryManager.onResponseReceived(
+                responseForServiceTypeTwo, ifIndex, NETWORK_1));
+        verify(mockServiceTypeClientType1NullNetwork, never()).processResponse(any(), anyInt(),
+                eq(NETWORK_1));
+        verify(mockServiceTypeClientType1Network1).processResponse(responseForServiceTypeTwo,
+                ifIndex, NETWORK_1);
+        verify(mockServiceTypeClientType2Network2, never()).processResponse(any(), anyInt(),
+                eq(NETWORK_1));
+
+        final MdnsPacket responseForSubtype =
+                createMdnsPacket("subtype._sub._googlecast._tcp.local");
+        runOnHandler(() -> discoveryManager.onResponseReceived(
+                responseForSubtype, ifIndex, NETWORK_2));
+        verify(mockServiceTypeClientType1NullNetwork, never()).processResponse(
+                any(), anyInt(), eq(NETWORK_2));
+        verify(mockServiceTypeClientType1Network1, never()).processResponse(
+                any(), anyInt(), eq(NETWORK_2));
+        verify(mockServiceTypeClientType2Network2).processResponse(
+                responseForSubtype, ifIndex, NETWORK_2);
     }
 
-    private MdnsResponse createMockResponse(String serviceType) {
-        MdnsPointerRecord mockPointerRecord = mock(MdnsPointerRecord.class);
-        MdnsResponse mockResponse = mock(MdnsResponse.class);
-        when(mockResponse.getPointerRecords())
-                .thenReturn(Collections.singletonList(mockPointerRecord));
-        when(mockPointerRecord.getName()).thenReturn(TextUtils.split(serviceType, "\\."));
-        return mockResponse;
+    @Test
+    public void testSocketCreatedAndDestroyed() throws IOException {
+        // Create a ServiceTypeClient for SERVICE_TYPE_1 and NETWORK_1
+        final MdnsSearchOptions network1Options =
+                MdnsSearchOptions.newBuilder().setNetwork(NETWORK_1).build();
+        final SocketCreationCallback callback = expectSocketCreationCallback(
+                SERVICE_TYPE_1, mockListenerOne, network1Options);
+        runOnHandler(() -> callback.onSocketCreated(NETWORK_1));
+        verify(mockServiceTypeClientType1Network1).startSendAndReceive(
+                mockListenerOne, network1Options);
+
+        // Create a ServiceTypeClient for SERVICE_TYPE_2 and NETWORK_1
+        final SocketCreationCallback callback2 = expectSocketCreationCallback(
+                SERVICE_TYPE_2, mockListenerTwo, network1Options);
+        runOnHandler(() -> callback2.onSocketCreated(NETWORK_1));
+        verify(mockServiceTypeClientType2Network1).startSendAndReceive(
+                mockListenerTwo, network1Options);
+
+        // Receive a response, it should be processed on both clients.
+        final MdnsPacket response = createMdnsPacket(SERVICE_TYPE_1);
+        final int ifIndex = 1;
+        runOnHandler(() -> discoveryManager.onResponseReceived(
+                response, ifIndex, NETWORK_1));
+        verify(mockServiceTypeClientType1Network1).processResponse(response, ifIndex, NETWORK_1);
+        verify(mockServiceTypeClientType2Network1).processResponse(response, ifIndex, NETWORK_1);
+
+        // The first callback receives a notification that the network has been destroyed,
+        // mockServiceTypeClientOne1 should send service removed notifications and remove from the
+        // list of clients.
+        runOnHandler(() -> callback.onAllSocketsDestroyed(NETWORK_1));
+        verify(mockServiceTypeClientType1Network1).notifySocketDestroyed();
+
+        // Receive a response again, it should be processed only on
+        // mockServiceTypeClientType2Network1. Because the mockServiceTypeClientType1Network1 is
+        // removed from the list of clients, it is no longer able to process responses.
+        runOnHandler(() -> discoveryManager.onResponseReceived(
+                response, ifIndex, NETWORK_1));
+        // Still times(1) as a response was received once previously
+        verify(mockServiceTypeClientType1Network1, times(1))
+                .processResponse(response, ifIndex, NETWORK_1);
+        verify(mockServiceTypeClientType2Network1, times(2))
+                .processResponse(response, ifIndex, NETWORK_1);
+
+        // The client for NETWORK_1 receives the callback that the NETWORK_2 has been destroyed,
+        // mockServiceTypeClientTwo2 shouldn't send any notifications.
+        runOnHandler(() -> callback2.onAllSocketsDestroyed(NETWORK_2));
+        verify(mockServiceTypeClientType2Network1, never()).notifySocketDestroyed();
+
+        // Receive a response again, mockServiceTypeClientType2Network1 is still in the list of
+        // clients, it's still able to process responses.
+        runOnHandler(() -> discoveryManager.onResponseReceived(
+                response, ifIndex, NETWORK_1));
+        verify(mockServiceTypeClientType1Network1, times(1))
+                .processResponse(response, ifIndex, NETWORK_1);
+        verify(mockServiceTypeClientType2Network1, times(3))
+                .processResponse(response, ifIndex, NETWORK_1);
+    }
+
+    private MdnsPacket createMdnsPacket(String serviceType) {
+        final String[] type = TextUtils.split(serviceType, "\\.");
+        final ArrayList<String> name = new ArrayList<>(type.length + 1);
+        name.add("TestName");
+        name.addAll(Arrays.asList(type));
+        return new MdnsPacket(0 /* flags */,
+                Collections.emptyList() /* questions */,
+                List.of(new MdnsPointerRecord(
+                        type,
+                        0L /* receiptTimeMillis */,
+                        false /* cacheFlush */,
+                        120000 /* ttlMillis */,
+                        name.toArray(new String[0])
+                        )) /* answers */,
+                Collections.emptyList() /* authorityRecords */,
+                Collections.emptyList() /* additionalRecords */);
     }
 }
\ No newline at end of file
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
new file mode 100644
index 0000000..dd458b8
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns
+
+import android.net.InetAddresses.parseNumericAddress
+import android.net.LinkAddress
+import android.net.nsd.NsdServiceInfo
+import android.os.Build
+import android.os.HandlerThread
+import com.android.net.module.util.HexDump
+import com.android.net.module.util.SharedLog
+import com.android.server.connectivity.mdns.MdnsAnnouncer.AnnouncementInfo
+import com.android.server.connectivity.mdns.MdnsAnnouncer.BaseAnnouncementInfo
+import com.android.server.connectivity.mdns.MdnsAnnouncer.ExitAnnouncementInfo
+import com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.EXIT_ANNOUNCEMENT_DELAY_MS
+import com.android.server.connectivity.mdns.MdnsPacketRepeater.PacketRepeaterCallback
+import com.android.server.connectivity.mdns.MdnsProber.ProbingInfo
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.waitForIdle
+import java.net.InetSocketAddress
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+private const val LOG_TAG = "testlogtag"
+private const val TIMEOUT_MS = 10_000L
+
+private val TEST_ADDRS = listOf(LinkAddress(parseNumericAddress("2001:db8::123"), 64))
+private val TEST_BUFFER = ByteArray(1300)
+private val TEST_HOSTNAME = arrayOf("Android_test", "local")
+
+private const val TEST_SERVICE_ID_1 = 42
+private val TEST_SERVICE_1 = NsdServiceInfo().apply {
+    serviceType = "_testservice._tcp"
+    serviceName = "MyTestService"
+    port = 12345
+}
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class MdnsInterfaceAdvertiserTest {
+    private val socket = mock(MdnsInterfaceSocket::class.java)
+    private val thread = HandlerThread(MdnsInterfaceAdvertiserTest::class.simpleName)
+    private val cb = mock(MdnsInterfaceAdvertiser.Callback::class.java)
+    private val deps = mock(MdnsInterfaceAdvertiser.Dependencies::class.java)
+    private val repository = mock(MdnsRecordRepository::class.java)
+    private val replySender = mock(MdnsReplySender::class.java)
+    private val announcer = mock(MdnsAnnouncer::class.java)
+    private val prober = mock(MdnsProber::class.java)
+    private val sharedlog = SharedLog("MdnsInterfaceAdvertiserTest")
+    @Suppress("UNCHECKED_CAST")
+    private val probeCbCaptor = ArgumentCaptor.forClass(PacketRepeaterCallback::class.java)
+            as ArgumentCaptor<PacketRepeaterCallback<ProbingInfo>>
+    @Suppress("UNCHECKED_CAST")
+    private val announceCbCaptor = ArgumentCaptor.forClass(PacketRepeaterCallback::class.java)
+            as ArgumentCaptor<PacketRepeaterCallback<BaseAnnouncementInfo>>
+    private val packetHandlerCaptor = ArgumentCaptor.forClass(
+            MulticastPacketReader.PacketHandler::class.java)
+
+    private val probeCb get() = probeCbCaptor.value
+    private val announceCb get() = announceCbCaptor.value
+    private val packetHandler get() = packetHandlerCaptor.value
+
+    private val advertiser by lazy {
+        MdnsInterfaceAdvertiser(
+            socket,
+            TEST_ADDRS,
+            thread.looper,
+            TEST_BUFFER,
+            cb,
+            deps,
+            TEST_HOSTNAME,
+            sharedlog
+        )
+    }
+
+    @Before
+    fun setUp() {
+        doReturn(repository).`when`(deps).makeRecordRepository(any(),
+            eq(TEST_HOSTNAME)
+        )
+        doReturn(replySender).`when`(deps).makeReplySender(anyString(), any(), any(), any())
+        doReturn(announcer).`when`(deps).makeMdnsAnnouncer(anyString(), any(), any(), any())
+        doReturn(prober).`when`(deps).makeMdnsProber(anyString(), any(), any(), any())
+
+        val knownServices = mutableSetOf<Int>()
+        doAnswer { inv ->
+            knownServices.add(inv.getArgument(0))
+
+            -1
+        }.`when`(repository).addService(anyInt(), any(), any())
+        doAnswer { inv ->
+            knownServices.remove(inv.getArgument(0))
+            null
+        }.`when`(repository).removeService(anyInt())
+        doAnswer {
+            knownServices.toIntArray().also { knownServices.clear() }
+        }.`when`(repository).clearServices()
+        doAnswer { inv ->
+            knownServices.contains(inv.getArgument(0))
+        }.`when`(repository).hasActiveService(anyInt())
+        thread.start()
+        advertiser.start()
+
+        verify(socket).addPacketHandler(packetHandlerCaptor.capture())
+        verify(deps).makeMdnsProber(any(), any(), any(), probeCbCaptor.capture())
+        verify(deps).makeMdnsAnnouncer(any(), any(), any(), announceCbCaptor.capture())
+    }
+
+    @After
+    fun tearDown() {
+        thread.quitSafely()
+        thread.join()
+    }
+
+    @Test
+    fun testAddRemoveService() {
+        val testAnnouncementInfo = addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+
+        verify(announcer).startSending(TEST_SERVICE_ID_1, testAnnouncementInfo,
+                0L /* initialDelayMs */)
+
+        thread.waitForIdle(TIMEOUT_MS)
+        verify(cb).onRegisterServiceSucceeded(advertiser, TEST_SERVICE_ID_1)
+
+        // Remove the service: expect exit announcements
+        val testExitInfo = mock(ExitAnnouncementInfo::class.java)
+        doReturn(testExitInfo).`when`(repository).exitService(TEST_SERVICE_ID_1)
+        advertiser.removeService(TEST_SERVICE_ID_1)
+
+        verify(prober).stop(TEST_SERVICE_ID_1)
+        verify(announcer).stop(TEST_SERVICE_ID_1)
+        verify(announcer).startSending(TEST_SERVICE_ID_1, testExitInfo, EXIT_ANNOUNCEMENT_DELAY_MS)
+
+        // Exit announcements finish: the advertiser has no left service and destroys itself
+        announceCb.onFinished(testExitInfo)
+        thread.waitForIdle(TIMEOUT_MS)
+        verify(cb).onDestroyed(socket)
+    }
+
+    @Test
+    fun testDoubleRemove() {
+        addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+
+        val testExitInfo = mock(ExitAnnouncementInfo::class.java)
+        doReturn(testExitInfo).`when`(repository).exitService(TEST_SERVICE_ID_1)
+        advertiser.removeService(TEST_SERVICE_ID_1)
+
+        verify(prober).stop(TEST_SERVICE_ID_1)
+        verify(announcer).stop(TEST_SERVICE_ID_1)
+        verify(announcer).startSending(TEST_SERVICE_ID_1, testExitInfo, EXIT_ANNOUNCEMENT_DELAY_MS)
+
+        doReturn(false).`when`(repository).hasActiveService(TEST_SERVICE_ID_1)
+        advertiser.removeService(TEST_SERVICE_ID_1)
+        // Prober, announcer were still stopped only one time
+        verify(prober, times(1)).stop(TEST_SERVICE_ID_1)
+        verify(announcer, times(1)).stop(TEST_SERVICE_ID_1)
+    }
+
+    @Test
+    fun testReplyToQuery() {
+        addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+
+        val mockReply = mock(MdnsRecordRepository.ReplyInfo::class.java)
+        doReturn(mockReply).`when`(repository).getReply(any(), any())
+
+        // Query obtained with:
+        // scapy.raw(scapy.DNS(
+        //  qd = scapy.DNSQR(qtype='PTR', qname='_testservice._tcp.local'))
+        // ).hex().upper()
+        val query = HexDump.hexStringToByteArray(
+                "0000010000010000000000000C5F7465737473657276696365045F746370056C6F63616C00000C0001"
+        )
+        val src = InetSocketAddress(parseNumericAddress("2001:db8::456"), MdnsConstants.MDNS_PORT)
+        packetHandler.handlePacket(query, query.size, src)
+
+        val packetCaptor = ArgumentCaptor.forClass(MdnsPacket::class.java)
+        verify(repository).getReply(packetCaptor.capture(), eq(src))
+
+        packetCaptor.value.let {
+            assertEquals(1, it.questions.size)
+            assertEquals(0, it.answers.size)
+            assertEquals(0, it.authorityRecords.size)
+            assertEquals(0, it.additionalRecords.size)
+
+            assertTrue(it.questions[0] is MdnsPointerRecord)
+            assertContentEquals(arrayOf("_testservice", "_tcp", "local"), it.questions[0].name)
+        }
+
+        verify(replySender).queueReply(mockReply)
+    }
+
+    @Test
+    fun testConflict() {
+        addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+        doReturn(setOf(TEST_SERVICE_ID_1)).`when`(repository).getConflictingServices(any())
+
+        // Reply obtained with:
+        // scapy.raw(scapy.DNS(
+        //    qd = None,
+        //    an = scapy.DNSRR(type='TXT', rrname='_testservice._tcp.local'))
+        // ).hex().upper()
+        val query = HexDump.hexStringToByteArray("0000010000000001000000000C5F7465737473657276696" +
+                "365045F746370056C6F63616C0000100001000000000000")
+        val src = InetSocketAddress(parseNumericAddress("2001:db8::456"), MdnsConstants.MDNS_PORT)
+        packetHandler.handlePacket(query, query.size, src)
+
+        val packetCaptor = ArgumentCaptor.forClass(MdnsPacket::class.java)
+        verify(repository).getConflictingServices(packetCaptor.capture())
+
+        packetCaptor.value.let {
+            assertEquals(0, it.questions.size)
+            assertEquals(1, it.answers.size)
+            assertEquals(0, it.authorityRecords.size)
+            assertEquals(0, it.additionalRecords.size)
+
+            assertTrue(it.answers[0] is MdnsTextRecord)
+            assertContentEquals(arrayOf("_testservice", "_tcp", "local"), it.answers[0].name)
+        }
+
+        thread.waitForIdle(TIMEOUT_MS)
+        verify(cb).onServiceConflict(advertiser, TEST_SERVICE_ID_1)
+    }
+
+    @Test
+    fun testRestartProbingForConflict() {
+        val mockProbingInfo = mock(ProbingInfo::class.java)
+        doReturn(mockProbingInfo).`when`(repository).setServiceProbing(TEST_SERVICE_ID_1)
+
+        advertiser.restartProbingForConflict(TEST_SERVICE_ID_1)
+
+        verify(prober).restartForConflict(mockProbingInfo)
+    }
+
+    @Test
+    fun testRenameServiceForConflict() {
+        val mockProbingInfo = mock(ProbingInfo::class.java)
+        doReturn(mockProbingInfo).`when`(repository).renameServiceForConflict(
+                TEST_SERVICE_ID_1, TEST_SERVICE_1)
+
+        advertiser.renameServiceForConflict(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+
+        verify(prober).restartForConflict(mockProbingInfo)
+    }
+
+    private fun addServiceAndFinishProbing(serviceId: Int, serviceInfo: NsdServiceInfo):
+            AnnouncementInfo {
+        val testProbingInfo = mock(ProbingInfo::class.java)
+        doReturn(serviceId).`when`(testProbingInfo).serviceId
+        doReturn(testProbingInfo).`when`(repository).setServiceProbing(serviceId)
+
+        advertiser.addService(serviceId, serviceInfo, null /* subtype */)
+        verify(repository).addService(serviceId, serviceInfo, null /* subtype */)
+        verify(prober).startProbing(testProbingInfo)
+
+        // Simulate probing success: continues to announcing
+        val testAnnouncementInfo = mock(AnnouncementInfo::class.java)
+        doReturn(testAnnouncementInfo).`when`(repository).onProbingSucceeded(testProbingInfo)
+        probeCb.onFinished(testProbingInfo)
+        return testAnnouncementInfo
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
new file mode 100644
index 0000000..87ba5d7
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
@@ -0,0 +1,320 @@
+/*
+ * 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 com.android.server.connectivity.mdns;
+
+import static com.android.server.connectivity.mdns.MdnsSocketProvider.SocketCallback;
+import static com.android.server.connectivity.mdns.MulticastPacketReader.PacketHandler;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.net.InetAddresses;
+import android.net.Network;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import com.android.net.module.util.HexDump;
+import com.android.server.connectivity.mdns.MdnsSocketClientBase.SocketCreationCallback;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.HandlerUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.net.DatagramPacket;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.List;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+public class MdnsMultinetworkSocketClientTest {
+    private static final byte[] BUFFER = new byte[10];
+    private static final long DEFAULT_TIMEOUT = 2000L;
+    @Mock private Network mNetwork;
+    @Mock private MdnsSocketProvider mProvider;
+    @Mock private MdnsInterfaceSocket mSocket;
+    @Mock private MdnsServiceBrowserListener mListener;
+    @Mock private MdnsSocketClientBase.Callback mCallback;
+    @Mock private SocketCreationCallback mSocketCreationCallback;
+    private MdnsMultinetworkSocketClient mSocketClient;
+    private Handler mHandler;
+
+    @Before
+    public void setUp() throws SocketException {
+        MockitoAnnotations.initMocks(this);
+        final HandlerThread thread = new HandlerThread("MdnsMultinetworkSocketClientTest");
+        thread.start();
+        mHandler = new Handler(thread.getLooper());
+        mSocketClient = new MdnsMultinetworkSocketClient(thread.getLooper(), mProvider);
+        mHandler.post(() -> mSocketClient.setCallback(mCallback));
+    }
+
+    private SocketCallback expectSocketCallback() {
+        return expectSocketCallback(mListener, mNetwork);
+    }
+
+    private SocketCallback expectSocketCallback(MdnsServiceBrowserListener listener,
+            Network requestedNetwork) {
+        final ArgumentCaptor<SocketCallback> callbackCaptor =
+                ArgumentCaptor.forClass(SocketCallback.class);
+        mHandler.post(() -> mSocketClient.notifyNetworkRequested(
+                listener, requestedNetwork, mSocketCreationCallback));
+        verify(mProvider, timeout(DEFAULT_TIMEOUT))
+                .requestSocket(eq(requestedNetwork), callbackCaptor.capture());
+        return callbackCaptor.getValue();
+    }
+
+    private NetworkInterface createEmptyNetworkInterface() {
+        try {
+            Constructor<NetworkInterface> constructor =
+                    NetworkInterface.class.getDeclaredConstructor();
+            constructor.setAccessible(true);
+            return constructor.newInstance();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Test
+    public void testSendPacket() throws IOException {
+        final SocketCallback callback = expectSocketCallback();
+        final DatagramPacket ipv4Packet = new DatagramPacket(BUFFER, 0 /* offset */, BUFFER.length,
+                InetAddresses.parseNumericAddress("192.0.2.1"), 0 /* port */);
+        final DatagramPacket ipv6Packet = new DatagramPacket(BUFFER, 0 /* offset */, BUFFER.length,
+                InetAddresses.parseNumericAddress("2001:db8::"), 0 /* port */);
+
+        final MdnsInterfaceSocket tetherIfaceSock1 = mock(MdnsInterfaceSocket.class);
+        final MdnsInterfaceSocket tetherIfaceSock2 = mock(MdnsInterfaceSocket.class);
+        for (MdnsInterfaceSocket socket : List.of(mSocket, tetherIfaceSock1, tetherIfaceSock2)) {
+            doReturn(true).when(socket).hasJoinedIpv4();
+            doReturn(true).when(socket).hasJoinedIpv6();
+            doReturn(createEmptyNetworkInterface()).when(socket).getInterface();
+        }
+
+        // Notify socket created
+        callback.onSocketCreated(mNetwork, mSocket, List.of());
+        verify(mSocketCreationCallback).onSocketCreated(mNetwork);
+        callback.onSocketCreated(null, tetherIfaceSock1, List.of());
+        verify(mSocketCreationCallback).onSocketCreated(null);
+        callback.onSocketCreated(null, tetherIfaceSock2, List.of());
+        verify(mSocketCreationCallback, times(2)).onSocketCreated(null);
+
+        // Send packet to IPv4 with target network and verify sending has been called.
+        mSocketClient.sendMulticastPacket(ipv4Packet, mNetwork);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mSocket).send(ipv4Packet);
+        verify(tetherIfaceSock1, never()).send(any());
+        verify(tetherIfaceSock2, never()).send(any());
+
+        // Send packet to IPv6 without target network and verify sending has been called.
+        mSocketClient.sendMulticastPacket(ipv6Packet, null);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mSocket, never()).send(ipv6Packet);
+        verify(tetherIfaceSock1).send(ipv6Packet);
+        verify(tetherIfaceSock2).send(ipv6Packet);
+    }
+
+    @Test
+    public void testReceivePacket() {
+        final SocketCallback callback = expectSocketCallback();
+        final byte[] data = HexDump.hexStringToByteArray(
+                // scapy.raw(scapy.dns_compress(
+                //     scapy.DNS(rd=0, qr=1, aa=1, qd = None,
+                //     an =
+                //     scapy.DNSRR(type='PTR', rrname='_testtype._tcp.local',
+                //         rdata='testservice._testtype._tcp.local', rclass='IN', ttl=4500) /
+                //     scapy.DNSRRSRV(rrname='testservice._testtype._tcp.local', rclass=0x8001,
+                //         port=31234, target='Android.local', ttl=120))
+                // )).hex().upper()
+                "000084000000000200000000095F7465737474797065045F746370056C6F63616C00000C0001000011"
+                        + "94000E0B7465737473657276696365C00CC02C00218001000000780010000000007A0207"
+                        + "416E64726F6964C01B");
+
+        doReturn(createEmptyNetworkInterface()).when(mSocket).getInterface();
+        // Notify socket created
+        callback.onSocketCreated(mNetwork, mSocket, List.of());
+        verify(mSocketCreationCallback).onSocketCreated(mNetwork);
+
+        final ArgumentCaptor<PacketHandler> handlerCaptor =
+                ArgumentCaptor.forClass(PacketHandler.class);
+        verify(mSocket).addPacketHandler(handlerCaptor.capture());
+
+        // Send the data and verify the received records.
+        final PacketHandler handler = handlerCaptor.getValue();
+        handler.handlePacket(data, data.length, null /* src */);
+        final ArgumentCaptor<MdnsPacket> responseCaptor =
+                ArgumentCaptor.forClass(MdnsPacket.class);
+        verify(mCallback).onResponseReceived(responseCaptor.capture(), anyInt(), any());
+        final MdnsPacket response = responseCaptor.getValue();
+        assertEquals(0, response.questions.size());
+        assertEquals(0, response.additionalRecords.size());
+        assertEquals(0, response.authorityRecords.size());
+
+        final String[] serviceName = "testservice._testtype._tcp.local".split("\\.");
+        assertEquals(List.of(
+                new MdnsPointerRecord("_testtype._tcp.local".split("\\."),
+                        0L /* receiptTimeMillis */, false /* cacheFlush */, 4500000 /* ttlMillis */,
+                        serviceName),
+                new MdnsServiceRecord(serviceName, 0L /* receiptTimeMillis */,
+                        false /* cacheFlush */, 4500000 /* ttlMillis */, 0 /* servicePriority */,
+                        0 /* serviceWeight */, 31234 /* servicePort */,
+                        new String[] { "Android", "local" } /* serviceHost */)
+        ), response.answers);
+    }
+
+    @Test
+    public void testSocketRemovedAfterNetworkUnrequested() throws IOException {
+        // Request sockets on all networks
+        final SocketCallback callback = expectSocketCallback(mListener, null);
+        final DatagramPacket ipv4Packet = new DatagramPacket(BUFFER, 0 /* offset */, BUFFER.length,
+                InetAddresses.parseNumericAddress("192.0.2.1"), 0 /* port */);
+
+        // Notify 3 socket created, including 2 tethered interfaces (null network)
+        final MdnsInterfaceSocket socket2 = mock(MdnsInterfaceSocket.class);
+        final MdnsInterfaceSocket socket3 = mock(MdnsInterfaceSocket.class);
+        doReturn(true).when(mSocket).hasJoinedIpv4();
+        doReturn(true).when(mSocket).hasJoinedIpv6();
+        doReturn(true).when(socket2).hasJoinedIpv4();
+        doReturn(true).when(socket2).hasJoinedIpv6();
+        doReturn(true).when(socket3).hasJoinedIpv4();
+        doReturn(true).when(socket3).hasJoinedIpv6();
+        doReturn(createEmptyNetworkInterface()).when(mSocket).getInterface();
+        doReturn(createEmptyNetworkInterface()).when(socket2).getInterface();
+        doReturn(createEmptyNetworkInterface()).when(socket3).getInterface();
+
+        callback.onSocketCreated(mNetwork, mSocket, List.of());
+        callback.onSocketCreated(null, socket2, List.of());
+        callback.onSocketCreated(null, socket3, List.of());
+        verify(mSocketCreationCallback).onSocketCreated(mNetwork);
+        verify(mSocketCreationCallback, times(2)).onSocketCreated(null);
+
+        // Send IPv4 packet on the non-null Network and verify sending has been called.
+        mSocketClient.sendMulticastPacket(ipv4Packet, mNetwork);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mSocket).send(ipv4Packet);
+        verify(socket2, never()).send(any());
+        verify(socket3, never()).send(any());
+
+        // Request another socket with null network, get the same interfaces
+        final SocketCreationCallback socketCreationCb2 = mock(SocketCreationCallback.class);
+        final MdnsServiceBrowserListener listener2 = mock(MdnsServiceBrowserListener.class);
+
+        // requestSocket is called a second time
+        final ArgumentCaptor<SocketCallback> callback2Captor =
+                ArgumentCaptor.forClass(SocketCallback.class);
+        mHandler.post(() -> mSocketClient.notifyNetworkRequested(
+                listener2, null, socketCreationCb2));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mProvider, times(2)).requestSocket(eq(null), callback2Captor.capture());
+        final SocketCallback callback2 = callback2Captor.getAllValues().get(1);
+
+        // Notify socket created for all networks.
+        callback2.onSocketCreated(mNetwork, mSocket, List.of());
+        callback2.onSocketCreated(null, socket2, List.of());
+        callback2.onSocketCreated(null, socket3, List.of());
+        verify(socketCreationCb2).onSocketCreated(mNetwork);
+        verify(socketCreationCb2, times(2)).onSocketCreated(null);
+
+        // Send IPv4 packet to null network and verify sending to the 2 tethered interface sockets.
+        mSocketClient.sendMulticastPacket(ipv4Packet, null);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        // ipv4Packet still sent only once on mSocket: times(1) matches the packet sent earlier on
+        // mNetwork
+        verify(mSocket, times(1)).send(ipv4Packet);
+        verify(socket2).send(ipv4Packet);
+        verify(socket3).send(ipv4Packet);
+
+        // Unregister the second request
+        mHandler.post(() -> mSocketClient.notifyNetworkUnrequested(listener2));
+        verify(mProvider, timeout(DEFAULT_TIMEOUT)).unrequestSocket(callback2);
+
+        // Send IPv4 packet again and verify it's still sent a second time
+        mSocketClient.sendMulticastPacket(ipv4Packet, null);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(socket2, times(2)).send(ipv4Packet);
+        verify(socket3, times(2)).send(ipv4Packet);
+
+        // Unrequest remaining sockets
+        mHandler.post(() -> mSocketClient.notifyNetworkUnrequested(mListener));
+        verify(mProvider, timeout(DEFAULT_TIMEOUT)).unrequestSocket(callback);
+
+        // Send IPv4 packet and verify no more sending.
+        mSocketClient.sendMulticastPacket(ipv4Packet, null);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mSocket, times(1)).send(ipv4Packet);
+        verify(socket2, times(2)).send(ipv4Packet);
+        verify(socket3, times(2)).send(ipv4Packet);
+    }
+
+    @Test
+    public void testNotifyNetworkUnrequested_SocketsOnNullNetwork() {
+        final MdnsInterfaceSocket otherSocket = mock(MdnsInterfaceSocket.class);
+        final SocketCallback callback = expectSocketCallback(
+                mListener, null /* requestedNetwork */);
+        doReturn(createEmptyNetworkInterface()).when(mSocket).getInterface();
+        doReturn(createEmptyNetworkInterface()).when(otherSocket).getInterface();
+
+        callback.onSocketCreated(null /* network */, mSocket, List.of());
+        verify(mSocketCreationCallback).onSocketCreated(null);
+        callback.onSocketCreated(null /* network */, otherSocket, List.of());
+        verify(mSocketCreationCallback, times(2)).onSocketCreated(null);
+
+        verify(mSocketCreationCallback, never()).onAllSocketsDestroyed(null /* network */);
+        mHandler.post(() -> mSocketClient.notifyNetworkUnrequested(mListener));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+
+        verify(mProvider).unrequestSocket(callback);
+        verify(mSocketCreationCallback).onAllSocketsDestroyed(null /* network */);
+    }
+
+    @Test
+    public void testSocketCreatedAndDestroyed_NullNetwork() throws IOException {
+        final MdnsInterfaceSocket otherSocket = mock(MdnsInterfaceSocket.class);
+        final SocketCallback callback = expectSocketCallback(mListener, null /* network */);
+        doReturn(createEmptyNetworkInterface()).when(mSocket).getInterface();
+        doReturn(createEmptyNetworkInterface()).when(otherSocket).getInterface();
+
+        callback.onSocketCreated(null /* network */, mSocket, List.of());
+        verify(mSocketCreationCallback).onSocketCreated(null);
+        callback.onSocketCreated(null /* network */, otherSocket, List.of());
+        verify(mSocketCreationCallback, times(2)).onSocketCreated(null);
+
+        // Notify socket destroyed
+        callback.onInterfaceDestroyed(null /* network */, mSocket);
+        verifyNoMoreInteractions(mSocketCreationCallback);
+        callback.onInterfaceDestroyed(null /* network */, otherSocket);
+        verify(mSocketCreationCallback).onAllSocketsDestroyed(null /* network */);
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt
new file mode 100644
index 0000000..f88da1f
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt
@@ -0,0 +1,70 @@
+/*
+ * 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 com.android.server.connectivity.mdns
+
+import android.net.InetAddresses
+import com.android.net.module.util.HexDump
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DevSdkIgnoreRunner::class)
+class MdnsPacketTest {
+    @Test
+    fun testParseQuery() {
+        // Probe packet with 1 question for Android.local, and 4 additionalRecords with 4 addresses
+        // for Android.local (similar to legacy mdnsresponder probes, although it used to put 4
+        // identical questions(!!) for Android.local when there were 4 addresses).
+        val packetHex = "00000000000100000004000007416e64726f6964056c6f63616c0000ff0001c00c000100" +
+                "01000000780004c000027bc00c001c000100000078001020010db8000000000000000000000123c0" +
+                "0c001c000100000078001020010db8000000000000000000000456c00c001c000100000078001020" +
+                "010db8000000000000000000000789"
+
+        val bytes = HexDump.hexStringToByteArray(packetHex)
+        val reader = MdnsPacketReader(bytes, bytes.size)
+        val packet = MdnsPacket.parse(reader)
+
+        assertEquals(1, packet.questions.size)
+        assertEquals(0, packet.answers.size)
+        assertEquals(4, packet.authorityRecords.size)
+        assertEquals(0, packet.additionalRecords.size)
+
+        val hostname = arrayOf("Android", "local")
+        packet.questions[0].let {
+            assertTrue(it is MdnsAnyRecord)
+            assertContentEquals(hostname, it.name)
+        }
+
+        packet.authorityRecords.forEach {
+            assertTrue(it is MdnsInetAddressRecord)
+            assertContentEquals(hostname, it.name)
+            assertEquals(120000, it.ttl)
+        }
+
+        assertEquals(InetAddresses.parseNumericAddress("192.0.2.123"),
+                (packet.authorityRecords[0] as MdnsInetAddressRecord).inet4Address)
+        assertEquals(InetAddresses.parseNumericAddress("2001:db8::123"),
+                (packet.authorityRecords[1] as MdnsInetAddressRecord).inet6Address)
+        assertEquals(InetAddresses.parseNumericAddress("2001:db8::456"),
+                (packet.authorityRecords[2] as MdnsInetAddressRecord).inet6Address)
+        assertEquals(InetAddresses.parseNumericAddress("2001:db8::789"),
+                (packet.authorityRecords[3] as MdnsInetAddressRecord).inet6Address)
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketWriterTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketWriterTest.kt
new file mode 100644
index 0000000..a545373
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketWriterTest.kt
@@ -0,0 +1,55 @@
+/*
+ * 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 com.android.server.connectivity.mdns
+
+import android.net.InetAddresses
+import android.os.Build
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import java.net.InetSocketAddress
+import kotlin.test.assertContentEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class MdnsPacketWriterTest {
+    @Test
+    fun testNameCompression() {
+        val writer = MdnsPacketWriter(ByteArray(1000))
+        writer.writeLabels(arrayOf("my", "FIRST", "name"))
+        writer.writeLabels(arrayOf("my", "second", "name"))
+        writer.writeLabels(arrayOf("other", "first", "name"))
+        writer.writeLabels(arrayOf("my", "second", "name"))
+        writer.writeLabels(arrayOf("unrelated"))
+
+        val packet = writer.getPacket(
+                InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::123"), 123))
+
+        // Each label takes length + 1. So "first.name" offset = 3, "name" offset = 9
+        val expected = "my".label() + "FIRST".label() + "name".label() + 0x00.toByte() +
+                // "my.second.name" offset = 15
+                "my".label() + "second".label() + byteArrayOf(0xC0.toByte(), 9) +
+                "other".label() + byteArrayOf(0xC0.toByte(), 3) +
+                byteArrayOf(0xC0.toByte(), 15) +
+                "unrelated".label() + 0x00.toByte()
+
+        assertContentEquals(expected, packet.data.copyOfRange(0, packet.length))
+    }
+}
+
+private fun String.label() = byteArrayOf(length.toByte()) + encodeToByteArray()
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
new file mode 100644
index 0000000..0a8d78d
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsProberTest.kt
@@ -0,0 +1,206 @@
+/*
+ * 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 com.android.server.connectivity.mdns
+
+import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.Looper
+import com.android.internal.util.HexDump
+import com.android.server.connectivity.mdns.MdnsProber.ProbingInfo
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import java.net.DatagramPacket
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.any
+import org.mockito.Mockito.atLeast
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+private const val TEST_TIMEOUT_MS = 10_000L
+private const val SHORT_TIMEOUT_MS = 200L
+
+private val TEST_SERVICE_NAME_1 = arrayOf("testservice", "_nmt", "_tcp", "local")
+private val TEST_SERVICE_NAME_2 = arrayOf("testservice2", "_nmt", "_tcp", "local")
+private val TEST_SERVICE_NAME_3 = arrayOf("Testservice", "_nmt", "_tcp", "local")
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class MdnsProberTest {
+    private val thread = HandlerThread(MdnsProberTest::class.simpleName)
+    private val socket = mock(MdnsInterfaceSocket::class.java)
+    @Suppress("UNCHECKED_CAST")
+    private val cb = mock(MdnsPacketRepeater.PacketRepeaterCallback::class.java)
+        as MdnsPacketRepeater.PacketRepeaterCallback<ProbingInfo>
+    private val buffer = ByteArray(1500)
+
+    @Before
+    fun setUp() {
+        doReturn(true).`when`(socket).hasJoinedIpv6()
+        thread.start()
+    }
+
+    @After
+    fun tearDown() {
+        thread.quitSafely()
+        thread.join()
+    }
+
+    private class TestProbeInfo(probeRecords: List<MdnsRecord>, private val delayMs: Long = 1L) :
+            ProbingInfo(1 /* serviceId */, probeRecords) {
+        // Just send the packets quickly. Timing-related tests for MdnsPacketRepeater are already
+        // done in MdnsAnnouncerTest.
+        override fun getDelayMs(nextIndex: Int) = delayMs
+    }
+
+    private class TestProber(
+        looper: Looper,
+        replySender: MdnsReplySender,
+        cb: PacketRepeaterCallback<ProbingInfo>
+    ) : MdnsProber("testiface", looper, replySender, cb) {
+        override fun getInitialDelay() = 0L
+    }
+
+    private fun assertProbesSent(probeInfo: TestProbeInfo, expectedHex: String) {
+        repeat(probeInfo.numSends) { i ->
+            verify(cb, timeout(TEST_TIMEOUT_MS)).onSent(i, probeInfo)
+            // If the probe interval is short, more than (i+1) probes may have been sent already
+            verify(socket, atLeast(i + 1)).send(any())
+        }
+
+        val captor = ArgumentCaptor.forClass(DatagramPacket::class.java)
+        // There should be exactly numSends probes sent at the end
+        verify(socket, times(probeInfo.numSends)).send(captor.capture())
+
+        captor.allValues.forEach {
+            assertEquals(expectedHex, HexDump.toHexString(it.data))
+        }
+        verify(cb, timeout(TEST_TIMEOUT_MS)).onFinished(probeInfo)
+    }
+
+    private fun makeServiceRecord(name: Array<String>, port: Int) = MdnsServiceRecord(
+            name,
+            0L /* receiptTimeMillis */,
+            false /* cacheFlush */,
+            120_000L /* ttlMillis */,
+            0 /* servicePriority */,
+            0 /* serviceWeight */,
+            port,
+            arrayOf("myhostname", "local"))
+
+    @Test
+    fun testProbe() {
+        val replySender = MdnsReplySender("testiface", thread.looper, socket, buffer)
+        val prober = TestProber(thread.looper, replySender, cb)
+        val probeInfo = TestProbeInfo(
+                listOf(makeServiceRecord(TEST_SERVICE_NAME_1, 37890)))
+        prober.startProbing(probeInfo)
+
+        // Inspect with python3:
+        // import scapy.all as scapy; scapy.DNS(bytes.fromhex('[bytes]')).show2()
+        val expected = "0000000000010000000100000B7465737473657276696365045F6E6D74045F746370056C" +
+                "6F63616C0000FF0001C00C002100010000007800130000000094020A6D79686F73746E616D65C022"
+        assertProbesSent(probeInfo, expected)
+    }
+
+    @Test
+    fun testCreateProberCaseInsensitive() {
+        val probeInfo = TestProbeInfo(
+            listOf(makeServiceRecord(TEST_SERVICE_NAME_1, 37890),
+                makeServiceRecord(TEST_SERVICE_NAME_2, 37890),
+                makeServiceRecord(TEST_SERVICE_NAME_3, 37890)))
+        assertEquals(2, probeInfo.getPacket(0).questions.size)
+    }
+
+    @Test
+    fun testProbeMultipleRecords() {
+        val replySender = MdnsReplySender("testiface", thread.looper, socket, buffer)
+        val prober = TestProber(thread.looper, replySender, cb)
+        val probeInfo = TestProbeInfo(listOf(
+                makeServiceRecord(TEST_SERVICE_NAME_1, 37890),
+                makeServiceRecord(TEST_SERVICE_NAME_2, 37891),
+                MdnsTextRecord(
+                        // Same name as the first record; there should not be 2 duplicated questions
+                        TEST_SERVICE_NAME_1,
+                        0L /* receiptTimeMillis */,
+                        false /* cacheFlush */,
+                        120_000L /* ttlMillis */,
+                        listOf(MdnsServiceInfo.TextEntry("testKey", "testValue")))))
+        prober.startProbing(probeInfo)
+
+        /*
+        Expected data obtained with:
+        scapy.raw(scapy.dns_compress(scapy.DNS(rd=0,
+            qd =
+                scapy.DNSQR(qname='testservice._nmt._tcp.local.', qtype='ALL') /
+                scapy.DNSQR(qname='testservice2._nmt._tcp.local.', qtype='ALL'),
+            ns=
+                scapy.DNSRRSRV(rrname='testservice._nmt._tcp.local.', type='SRV', ttl=120,
+                    port=37890, target='myhostname.local.') /
+                scapy.DNSRRSRV(rrname='testservice2._nmt._tcp.local.', type='SRV', ttl=120,
+                    port=37891, target='myhostname.local.') /
+                scapy.DNSRR(type='TXT', ttl=120, rrname='testservice._nmt._tcp.local.',
+                    rdata='testKey=testValue'))
+        )).hex().upper()
+         */
+        val expected = "0000000000020000000300000B7465737473657276696365045F6E6D74045F746370056C6" +
+                "F63616C0000FF00010C746573747365727669636532C01800FF0001C00C002100010000007800130" +
+                "000000094020A6D79686F73746E616D65C022C02D00210001000000780008000000009403C052C00" +
+                "C0010000100000078001211746573744B65793D7465737456616C7565"
+        assertProbesSent(probeInfo, expected)
+    }
+
+    @Test
+    fun testStopProbing() {
+        val replySender = MdnsReplySender("testiface", thread.looper, socket, buffer)
+        val prober = TestProber(thread.looper, replySender, cb)
+        val probeInfo = TestProbeInfo(
+                listOf(makeServiceRecord(TEST_SERVICE_NAME_1, 37890)),
+                // delayMs is the delay between each probe, so does not apply to the first one
+                delayMs = SHORT_TIMEOUT_MS)
+        prober.startProbing(probeInfo)
+
+        // Expect the initial probe
+        verify(cb, timeout(TEST_TIMEOUT_MS)).onSent(0, probeInfo)
+
+        // Stop probing
+        val stopResult = CompletableFuture<Boolean>()
+        Handler(thread.looper).post { stopResult.complete(prober.stop(probeInfo.serviceId)) }
+        assertTrue(stopResult.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS),
+                "stop should return true when probing was in progress")
+
+        // Wait for a bit (more than the probe delay) to ensure no more probes were sent
+        Thread.sleep(SHORT_TIMEOUT_MS * 2)
+        verify(cb, never()).onSent(1, probeInfo)
+        verify(cb, never()).onFinished(probeInfo)
+
+        // Only one sent packet
+        verify(socket, times(1)).send(any())
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
new file mode 100644
index 0000000..0033b5a
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
@@ -0,0 +1,620 @@
+/*
+ * 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 com.android.server.connectivity.mdns
+
+import android.net.InetAddresses.parseNumericAddress
+import android.net.LinkAddress
+import android.net.nsd.NsdServiceInfo
+import android.os.Build
+import android.os.HandlerThread
+import com.android.server.connectivity.mdns.MdnsAnnouncer.AnnouncementInfo
+import com.android.server.connectivity.mdns.MdnsRecordRepository.Dependencies
+import com.android.server.connectivity.mdns.MdnsRecordRepository.getReverseDnsAddress
+import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import java.net.InetSocketAddress
+import java.net.NetworkInterface
+import java.util.Collections
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TEST_SERVICE_ID_1 = 42
+private const val TEST_SERVICE_ID_2 = 43
+private const val TEST_SERVICE_ID_3 = 44
+private const val TEST_PORT = 12345
+private const val TEST_SUBTYPE = "_subtype"
+private val TEST_HOSTNAME = arrayOf("Android_000102030405060708090A0B0C0D0E0F", "local")
+private val TEST_ADDRESSES = listOf(
+        LinkAddress(parseNumericAddress("192.0.2.111"), 24),
+        LinkAddress(parseNumericAddress("2001:db8::111"), 64),
+        LinkAddress(parseNumericAddress("2001:db8::222"), 64))
+
+private val TEST_SERVICE_1 = NsdServiceInfo().apply {
+    serviceType = "_testservice._tcp"
+    serviceName = "MyTestService"
+    port = TEST_PORT
+}
+
+private val TEST_SERVICE_2 = NsdServiceInfo().apply {
+    serviceType = "_testservice._tcp"
+    serviceName = "MyOtherTestService"
+    port = TEST_PORT
+}
+
+private val TEST_SERVICE_3 = NsdServiceInfo().apply {
+    serviceType = "_TESTSERVICE._tcp"
+    serviceName = "MyTESTSERVICE"
+    port = TEST_PORT
+}
+
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class MdnsRecordRepositoryTest {
+    private val thread = HandlerThread(MdnsRecordRepositoryTest::class.simpleName)
+    private val deps = object : Dependencies() {
+        override fun getInterfaceInetAddresses(iface: NetworkInterface) =
+                Collections.enumeration(TEST_ADDRESSES.map { it.address })
+    }
+
+    @Before
+    fun setUp() {
+        thread.start()
+    }
+
+    @After
+    fun tearDown() {
+        thread.quitSafely()
+        thread.join()
+    }
+
+    @Test
+    fun testAddServiceAndProbe() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+        assertEquals(0, repository.servicesCount)
+        assertEquals(-1, repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1,
+                null /* subtype */))
+        assertEquals(1, repository.servicesCount)
+
+        val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
+        assertNotNull(probingInfo)
+        assertTrue(repository.isProbing(TEST_SERVICE_ID_1))
+
+        assertEquals(TEST_SERVICE_ID_1, probingInfo.serviceId)
+        val packet = probingInfo.getPacket(0)
+
+        assertEquals(MdnsConstants.FLAGS_QUERY, packet.flags)
+        assertEquals(0, packet.answers.size)
+        assertEquals(0, packet.additionalRecords.size)
+
+        assertEquals(1, packet.questions.size)
+        val expectedName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
+        assertEquals(MdnsAnyRecord(expectedName, false /* unicast */), packet.questions[0])
+
+        assertEquals(1, packet.authorityRecords.size)
+        assertEquals(MdnsServiceRecord(expectedName,
+                0L /* receiptTimeMillis */,
+                false /* cacheFlush */,
+                120_000L /* ttlMillis */,
+                0 /* servicePriority */, 0 /* serviceWeight */,
+                TEST_PORT, TEST_HOSTNAME), packet.authorityRecords[0])
+
+        assertContentEquals(intArrayOf(TEST_SERVICE_ID_1), repository.clearServices())
+    }
+
+    @Test
+    fun testAddAndConflicts() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
+        assertFailsWith(NameConflictException::class) {
+            repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1, null /* subtype */)
+        }
+        assertFailsWith(NameConflictException::class) {
+            repository.addService(TEST_SERVICE_ID_3, TEST_SERVICE_3, null /* subtype */)
+        }
+    }
+
+    @Test
+    fun testInvalidReuseOfServiceId() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
+        assertFailsWith(IllegalArgumentException::class) {
+            repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_2, null /* subtype */)
+        }
+    }
+
+    @Test
+    fun testHasActiveService() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+        assertFalse(repository.hasActiveService(TEST_SERVICE_ID_1))
+
+        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
+        assertTrue(repository.hasActiveService(TEST_SERVICE_ID_1))
+
+        val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
+        repository.onProbingSucceeded(probingInfo)
+        repository.onAdvertisementSent(TEST_SERVICE_ID_1)
+        assertTrue(repository.hasActiveService(TEST_SERVICE_ID_1))
+
+        repository.exitService(TEST_SERVICE_ID_1)
+        assertFalse(repository.hasActiveService(TEST_SERVICE_ID_1))
+    }
+
+    @Test
+    fun testExitAnnouncements() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+        repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+        repository.onAdvertisementSent(TEST_SERVICE_ID_1)
+
+        val exitAnnouncement = repository.exitService(TEST_SERVICE_ID_1)
+        assertNotNull(exitAnnouncement)
+        assertEquals(1, repository.servicesCount)
+        val packet = exitAnnouncement.getPacket(0)
+
+        assertEquals(0x8400 /* response, authoritative */, packet.flags)
+        assertEquals(0, packet.questions.size)
+        assertEquals(0, packet.authorityRecords.size)
+        assertEquals(0, packet.additionalRecords.size)
+
+        assertContentEquals(listOf(
+                MdnsPointerRecord(
+                        arrayOf("_testservice", "_tcp", "local"),
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        0L /* ttlMillis */,
+                        arrayOf("MyTestService", "_testservice", "_tcp", "local"))
+        ), packet.answers)
+
+        repository.removeService(TEST_SERVICE_ID_1)
+        assertEquals(0, repository.servicesCount)
+    }
+
+    @Test
+    fun testExitAnnouncements_WithSubtype() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+        repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, TEST_SUBTYPE)
+        repository.onAdvertisementSent(TEST_SERVICE_ID_1)
+
+        val exitAnnouncement = repository.exitService(TEST_SERVICE_ID_1)
+        assertNotNull(exitAnnouncement)
+        assertEquals(1, repository.servicesCount)
+        val packet = exitAnnouncement.getPacket(0)
+
+        assertEquals(0x8400 /* response, authoritative */, packet.flags)
+        assertEquals(0, packet.questions.size)
+        assertEquals(0, packet.authorityRecords.size)
+        assertEquals(0, packet.additionalRecords.size)
+
+        assertContentEquals(listOf(
+                MdnsPointerRecord(
+                        arrayOf("_testservice", "_tcp", "local"),
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        0L /* ttlMillis */,
+                        arrayOf("MyTestService", "_testservice", "_tcp", "local")),
+                MdnsPointerRecord(
+                        arrayOf("_subtype", "_sub", "_testservice", "_tcp", "local"),
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        0L /* ttlMillis */,
+                        arrayOf("MyTestService", "_testservice", "_tcp", "local")),
+        ), packet.answers)
+
+        repository.removeService(TEST_SERVICE_ID_1)
+        assertEquals(0, repository.servicesCount)
+    }
+
+    @Test
+    fun testExitingServiceReAdded() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+        repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+        repository.onAdvertisementSent(TEST_SERVICE_ID_1)
+        repository.exitService(TEST_SERVICE_ID_1)
+
+        assertEquals(TEST_SERVICE_ID_1,
+                repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1, null /* subtype */))
+        assertEquals(1, repository.servicesCount)
+
+        repository.removeService(TEST_SERVICE_ID_2)
+        assertEquals(0, repository.servicesCount)
+    }
+
+    @Test
+    fun testOnProbingSucceeded() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+        val announcementInfo = repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1,
+                TEST_SUBTYPE)
+        repository.onAdvertisementSent(TEST_SERVICE_ID_1)
+        val packet = announcementInfo.getPacket(0)
+
+        assertEquals(0x8400 /* response, authoritative */, packet.flags)
+        assertEquals(0, packet.questions.size)
+        assertEquals(0, packet.authorityRecords.size)
+
+        val serviceType = arrayOf("_testservice", "_tcp", "local")
+        val serviceSubtype = arrayOf(TEST_SUBTYPE, "_sub", "_testservice", "_tcp", "local")
+        val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
+        val v4AddrRev = getReverseDnsAddress(TEST_ADDRESSES[0].address)
+        val v6Addr1Rev = getReverseDnsAddress(TEST_ADDRESSES[1].address)
+        val v6Addr2Rev = getReverseDnsAddress(TEST_ADDRESSES[2].address)
+
+        assertContentEquals(listOf(
+                // Reverse address and address records for the hostname
+                MdnsPointerRecord(v4AddrRev,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        TEST_HOSTNAME),
+                MdnsInetAddressRecord(TEST_HOSTNAME,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        TEST_ADDRESSES[0].address),
+                MdnsPointerRecord(v6Addr1Rev,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        TEST_HOSTNAME),
+                MdnsInetAddressRecord(TEST_HOSTNAME,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        TEST_ADDRESSES[1].address),
+                MdnsPointerRecord(v6Addr2Rev,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        TEST_HOSTNAME),
+                MdnsInetAddressRecord(TEST_HOSTNAME,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        TEST_ADDRESSES[2].address),
+                // Service registration records (RFC6763)
+                MdnsPointerRecord(
+                        serviceType,
+                        0L /* receiptTimeMillis */,
+                        // Not a unique name owned by the announcer, so cacheFlush=false
+                        false /* cacheFlush */,
+                        4500000L /* ttlMillis */,
+                        serviceName),
+                MdnsPointerRecord(
+                        serviceSubtype,
+                        0L /* receiptTimeMillis */,
+                        // Not a unique name owned by the announcer, so cacheFlush=false
+                        false /* cacheFlush */,
+                        4500000L /* ttlMillis */,
+                        serviceName),
+                MdnsServiceRecord(
+                        serviceName,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        0 /* servicePriority */,
+                        0 /* serviceWeight */,
+                        TEST_PORT /* servicePort */,
+                        TEST_HOSTNAME),
+                MdnsTextRecord(
+                        serviceName,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        4500000L /* ttlMillis */,
+                        emptyList() /* entries */),
+                // Service type enumeration record (RFC6763 9.)
+                MdnsPointerRecord(
+                        arrayOf("_services", "_dns-sd", "_udp", "local"),
+                        0L /* receiptTimeMillis */,
+                        false /* cacheFlush */,
+                        4500000L /* ttlMillis */,
+                        serviceType)
+        ), packet.answers)
+
+        assertContentEquals(listOf(
+                MdnsNsecRecord(v4AddrRev,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        v4AddrRev,
+                        intArrayOf(MdnsRecord.TYPE_PTR)),
+                MdnsNsecRecord(TEST_HOSTNAME,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        TEST_HOSTNAME,
+                        intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)),
+                MdnsNsecRecord(v6Addr1Rev,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        v6Addr1Rev,
+                        intArrayOf(MdnsRecord.TYPE_PTR)),
+                MdnsNsecRecord(v6Addr2Rev,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        120000L /* ttlMillis */,
+                        v6Addr2Rev,
+                        intArrayOf(MdnsRecord.TYPE_PTR)),
+                MdnsNsecRecord(serviceName,
+                        0L /* receiptTimeMillis */,
+                        true /* cacheFlush */,
+                        4500000L /* ttlMillis */,
+                        serviceName,
+                        intArrayOf(MdnsRecord.TYPE_TXT, MdnsRecord.TYPE_SRV))
+        ), packet.additionalRecords)
+    }
+
+    @Test
+    fun testGetReverseDnsAddress() {
+        val expectedV6 = "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.B.D.0.1.0.0.2.ip6.arpa"
+                .split(".").toTypedArray()
+        assertContentEquals(expectedV6, getReverseDnsAddress(parseNumericAddress("2001:db8::1")))
+        val expectedV4 = "123.2.0.192.in-addr.arpa".split(".").toTypedArray()
+        assertContentEquals(expectedV4, getReverseDnsAddress(parseNumericAddress("192.0.2.123")))
+    }
+
+    @Test
+    fun testGetReplyCaseInsensitive() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+        repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+        val questionsCaseInSensitive =
+            listOf(MdnsPointerRecord(arrayOf("_TESTSERVICE", "_TCP", "local"),
+                0L /* receiptTimeMillis */,
+                false /* cacheFlush */,
+                // TTL and data is empty for a question
+                0L /* ttlMillis */,
+                null /* pointer */))
+        val queryCaseInsensitive = MdnsPacket(0 /* flags */, questionsCaseInSensitive,
+            listOf() /* answers */, listOf() /* authorityRecords */,
+            listOf() /* additionalRecords */)
+        val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
+        val replyCaseInsensitive = repository.getReply(queryCaseInsensitive, src)
+        assertNotNull(replyCaseInsensitive)
+        assertEquals(1, replyCaseInsensitive.answers.size)
+        assertEquals(7, replyCaseInsensitive.additionalAnswers.size)
+    }
+
+    @Test
+    fun testGetReply() {
+        doGetReplyTest(subtype = null)
+    }
+
+    @Test
+    fun testGetReply_WithSubtype() {
+        doGetReplyTest(TEST_SUBTYPE)
+    }
+
+    private fun doGetReplyTest(subtype: String?) {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+        repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, subtype)
+        val queriedName = if (subtype == null) arrayOf("_testservice", "_tcp", "local")
+        else arrayOf(subtype, "_sub", "_testservice", "_tcp", "local")
+
+        val questions = listOf(MdnsPointerRecord(queriedName,
+                0L /* receiptTimeMillis */,
+                false /* cacheFlush */,
+                // TTL and data is empty for a question
+                0L /* ttlMillis */,
+                null /* pointer */))
+        val query = MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
+                listOf() /* authorityRecords */, listOf() /* additionalRecords */)
+        val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
+        val reply = repository.getReply(query, src)
+
+        assertNotNull(reply)
+        // Source address is IPv4
+        assertEquals(MdnsConstants.getMdnsIPv4Address(), reply.destination.address)
+        assertEquals(MdnsConstants.MDNS_PORT, reply.destination.port)
+
+        // TTLs as per RFC6762 10.
+        val longTtl = 4_500_000L
+        val shortTtl = 120_000L
+        val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
+
+        assertEquals(listOf(
+                MdnsPointerRecord(
+                        queriedName,
+                        0L /* receiptTimeMillis */,
+                        false /* cacheFlush */,
+                        longTtl,
+                        serviceName),
+        ), reply.answers)
+
+        assertEquals(listOf(
+            MdnsTextRecord(
+                    serviceName,
+                    0L /* receiptTimeMillis */,
+                    true /* cacheFlush */,
+                    longTtl,
+                    listOf() /* entries */),
+            MdnsServiceRecord(
+                    serviceName,
+                    0L /* receiptTimeMillis */,
+                    true /* cacheFlush */,
+                    shortTtl,
+                    0 /* servicePriority */,
+                    0 /* serviceWeight */,
+                    TEST_PORT,
+                    TEST_HOSTNAME),
+            MdnsInetAddressRecord(
+                    TEST_HOSTNAME,
+                    0L /* receiptTimeMillis */,
+                    true /* cacheFlush */,
+                    shortTtl,
+                    TEST_ADDRESSES[0].address),
+            MdnsInetAddressRecord(
+                    TEST_HOSTNAME,
+                    0L /* receiptTimeMillis */,
+                    true /* cacheFlush */,
+                    shortTtl,
+                    TEST_ADDRESSES[1].address),
+            MdnsInetAddressRecord(
+                    TEST_HOSTNAME,
+                    0L /* receiptTimeMillis */,
+                    true /* cacheFlush */,
+                    shortTtl,
+                    TEST_ADDRESSES[2].address),
+            MdnsNsecRecord(
+                    serviceName,
+                    0L /* receiptTimeMillis */,
+                    true /* cacheFlush */,
+                    longTtl,
+                    serviceName /* nextDomain */,
+                    intArrayOf(MdnsRecord.TYPE_TXT, MdnsRecord.TYPE_SRV)),
+            MdnsNsecRecord(
+                    TEST_HOSTNAME,
+                    0L /* receiptTimeMillis */,
+                    true /* cacheFlush */,
+                    shortTtl,
+                    TEST_HOSTNAME /* nextDomain */,
+                    intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)),
+        ), reply.additionalAnswers)
+    }
+
+    @Test
+    fun testGetConflictingServices() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
+        repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* subtype */)
+
+        val packet = MdnsPacket(
+                0 /* flags */,
+                emptyList() /* questions */,
+                listOf(
+                    MdnsServiceRecord(
+                            arrayOf("MyTestService", "_testservice", "_tcp", "local"),
+                            0L /* receiptTimeMillis */, true /* cacheFlush */, 0L /* ttlMillis */,
+                            0 /* servicePriority */, 0 /* serviceWeight */,
+                            TEST_SERVICE_1.port + 1,
+                            TEST_HOSTNAME),
+                    MdnsTextRecord(
+                            arrayOf("MyOtherTestService", "_testservice", "_tcp", "local"),
+                            0L /* receiptTimeMillis */, true /* cacheFlush */, 0L /* ttlMillis */,
+                            listOf(TextEntry.fromString("somedifferent=entry"))),
+                ) /* answers */,
+                emptyList() /* authorityRecords */,
+                emptyList() /* additionalRecords */)
+
+        assertEquals(setOf(TEST_SERVICE_ID_1, TEST_SERVICE_ID_2),
+                repository.getConflictingServices(packet))
+    }
+
+    @Test
+    fun testGetConflictingServicesCaseInsensitive() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
+        repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* subtype */)
+
+        val packet = MdnsPacket(
+            0 /* flags */,
+            emptyList() /* questions */,
+            listOf(
+                MdnsServiceRecord(
+                    arrayOf("MYTESTSERVICE", "_TESTSERVICE", "_tcp", "local"),
+                    0L /* receiptTimeMillis */, true /* cacheFlush */, 0L /* ttlMillis */,
+                    0 /* servicePriority */, 0 /* serviceWeight */,
+                    TEST_SERVICE_1.port + 1,
+                    TEST_HOSTNAME),
+                MdnsTextRecord(
+                    arrayOf("MYOTHERTESTSERVICE", "_TESTSERVICE", "_tcp", "local"),
+                    0L /* receiptTimeMillis */, true /* cacheFlush */, 0L /* ttlMillis */,
+                    listOf(TextEntry.fromString("somedifferent=entry"))),
+            ) /* answers */,
+            emptyList() /* authorityRecords */,
+            emptyList() /* additionalRecords */)
+
+        assertEquals(setOf(TEST_SERVICE_ID_1, TEST_SERVICE_ID_2),
+            repository.getConflictingServices(packet))
+    }
+
+    @Test
+    fun testGetConflictingServices_IdenticalService() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
+        repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* subtype */)
+
+        val otherTtlMillis = 1234L
+        val packet = MdnsPacket(
+                0 /* flags */,
+                emptyList() /* questions */,
+                listOf(
+                        MdnsServiceRecord(
+                                arrayOf("MyTestService", "_testservice", "_tcp", "local"),
+                                0L /* receiptTimeMillis */, true /* cacheFlush */,
+                                otherTtlMillis, 0 /* servicePriority */, 0 /* serviceWeight */,
+                                TEST_SERVICE_1.port,
+                                arrayOf("ANDROID_000102030405060708090A0B0C0D0E0F", "local")),
+                        MdnsTextRecord(
+                                arrayOf("MyOtherTestService", "_testservice", "_tcp", "local"),
+                                0L /* receiptTimeMillis */, true /* cacheFlush */,
+                                otherTtlMillis, emptyList()),
+                ) /* answers */,
+                emptyList() /* authorityRecords */,
+                emptyList() /* additionalRecords */)
+
+        // Above records are identical to the actual registrations: no conflict
+        assertEquals(emptySet(), repository.getConflictingServices(packet))
+    }
+
+    @Test
+    fun testGetConflictingServicesCaseInsensitive_IdenticalService() {
+        val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
+        repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
+        repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* subtype */)
+
+        val otherTtlMillis = 1234L
+        val packet = MdnsPacket(
+                0 /* flags */,
+                emptyList() /* questions */,
+                listOf(
+                        MdnsServiceRecord(
+                                arrayOf("MYTESTSERVICE", "_TESTSERVICE", "_tcp", "local"),
+                                0L /* receiptTimeMillis */, true /* cacheFlush */,
+                                otherTtlMillis, 0 /* servicePriority */, 0 /* serviceWeight */,
+                                TEST_SERVICE_1.port,
+                                TEST_HOSTNAME),
+                        MdnsTextRecord(
+                                arrayOf("MyOtherTestService", "_TESTSERVICE", "_tcp", "local"),
+                                0L /* receiptTimeMillis */, true /* cacheFlush */,
+                                otherTtlMillis, emptyList()),
+                ) /* answers */,
+                emptyList() /* authorityRecords */,
+                emptyList() /* additionalRecords */)
+
+        // Above records are identical to the actual registrations: no conflict
+        assertEquals(emptySet(), repository.getConflictingServices(packet))
+    }
+}
+
+private fun MdnsRecordRepository.initWithService(
+    serviceId: Int,
+    serviceInfo: NsdServiceInfo,
+    subtype: String? = null
+): AnnouncementInfo {
+    updateAddresses(TEST_ADDRESSES)
+    addService(serviceId, serviceInfo, subtype)
+    val probingInfo = setServiceProbing(serviceId)
+    assertNotNull(probingInfo)
+    return onProbingSucceeded(probingInfo)
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTest.kt
new file mode 100644
index 0000000..8f819b7
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns
+
+import android.os.Build
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertEquals
+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.S_V2)
+class MdnsRecordTest {
+
+    @Test
+    fun testPointerRecordHasSubType() {
+        val ptrRecord1 = MdnsPointerRecord(
+            arrayOf("_testtype", "_sub", "_tcp", "local"),
+            0L /* receiptTimeMillis */,
+            false /* cacheFlush */,
+            4500000 /* ttlMillis */,
+            arrayOf("testservice", "_testtype", "_tcp", "local")
+        )
+        val ptrRecord2 = MdnsPointerRecord(
+            arrayOf("_testtype", "_SUB", "_tcp", "local"),
+            0L /* receiptTimeMillis */,
+            false /* cacheFlush */,
+            4500000 /* ttlMillis */,
+            arrayOf("testservice", "_testtype", "_tcp", "local")
+        )
+        assertTrue(ptrRecord1.hasSubtype())
+        assertTrue(ptrRecord2.hasSubtype())
+    }
+
+    @Test
+    fun testEqualsCaseInsensitive() {
+        val ptrRecord1 = MdnsPointerRecord(
+            arrayOf("_testtype", "_tcp", "local"),
+            0L /* receiptTimeMillis */,
+            false /* cacheFlush */,
+            4500000 /* ttlMillis */,
+            arrayOf("testservice", "_testtype", "_tcp", "local")
+            )
+        val ptrRecord2 = MdnsPointerRecord(
+            arrayOf("_testType", "_tcp", "local"),
+            0L /* receiptTimeMillis */,
+            false /* cacheFlush */,
+            4500000 /* ttlMillis */,
+            arrayOf("testsErvice", "_testtype", "_Tcp", "local")
+        )
+        assertEquals(ptrRecord1, ptrRecord2)
+        assertEquals(ptrRecord1.hashCode(), ptrRecord2.hashCode())
+
+        val srvRecord1 = MdnsServiceRecord(
+            arrayOf("testservice", "_testtype", "_tcp", "local"),
+            123 /* receiptTimeMillis */,
+            false /* cacheFlush */,
+            2000 /* ttlMillis */,
+            0 /* servicePriority */,
+            0 /* serviceWeight */,
+            80 /* port */,
+            arrayOf("hostname")
+        )
+        val srvRecord2 = MdnsServiceRecord(
+            arrayOf("Testservice", "_testtype", "_tcp", "local"),
+            123 /* receiptTimeMillis */,
+            false /* cacheFlush */,
+            2000 /* ttlMillis */,
+            0 /* servicePriority */,
+            0 /* serviceWeight */,
+            80 /* port */,
+            arrayOf("Hostname")
+        )
+        assertEquals(srvRecord1, srvRecord2)
+        assertEquals(srvRecord1.hashCode(), srvRecord2.hashCode())
+
+        val nsecRecord1 = MdnsNsecRecord(
+            arrayOf("hostname"),
+            0L /* receiptTimeMillis */,
+            true /* cacheFlush */,
+            2000L, /* ttlMillis */
+            arrayOf("hostname"),
+            intArrayOf(1, 2, 3) /* types */
+        )
+        val nsecRecord2 = MdnsNsecRecord(
+            arrayOf("HOSTNAME"),
+            0L /* receiptTimeMillis */,
+            true /* cacheFlush */,
+            2000L, /* ttlMillis */
+            arrayOf("HOSTNAME"),
+            intArrayOf(1, 2, 3) /* types */
+        )
+        assertEquals(nsecRecord1, nsecRecord2)
+        assertEquals(nsecRecord1.hashCode(), nsecRecord2.hashCode())
+    }
+
+    @Test
+    fun testLabelsAreSuffix() {
+        val labels1 = arrayOf("a", "b", "c")
+        val labels2 = arrayOf("B", "C")
+        val labels3 = arrayOf("b", "c")
+        val labels4 = arrayOf("b", "d")
+        assertTrue(MdnsRecord.labelsAreSuffix(labels2, labels1))
+        assertTrue(MdnsRecord.labelsAreSuffix(labels3, labels1))
+        assertFalse(MdnsRecord.labelsAreSuffix(labels4, labels1))
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java
index 7d800d8..55c2846 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java
@@ -281,9 +281,9 @@
                         // TTL 0x0000003c (60 secs)
                         + "0000003C"
                         // Data length
-                        + "003C"
-                        // nextdomain.android.com
-                        + "0A6E657874646F6D61696E07616E64726F696403636F6D00"
+                        + "0031"
+                        // nextdomain.android.com, with compression for android.com
+                        + "0A6E657874646F6D61696EC007"
                         // Type bitmaps: window block 0x00, bitmap length 0x05,
                         // bits 16 (TXT) and 33 (SRV) set: 0x0000800040
                         + "00050000800040"
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
index 4cae447..e16c448 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
@@ -16,20 +16,26 @@
 
 package com.android.server.connectivity.mdns;
 
+import static android.net.InetAddresses.parseNumericAddress;
+
 import static com.android.server.connectivity.mdns.MdnsResponseDecoder.Clock;
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 
-import android.net.InetAddresses;
 import android.net.Network;
+import android.util.ArraySet;
 
 import com.android.net.module.util.HexDump;
+import com.android.server.connectivity.mdns.MdnsResponseTests.MdnsInet4AddressRecord;
+import com.android.server.connectivity.mdns.MdnsResponseTests.MdnsInet6AddressRecord;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
@@ -43,8 +49,11 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetSocketAddress;
-import java.util.LinkedList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 @RunWith(DevSdkIgnoreRunner.class)
 @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
@@ -147,6 +156,48 @@
             + "010001000000780004C0A8018A0000000000000000000000000000"
             + "000000");
 
+    // MDNS record for name "testhost1" with an IPv4 address of 10.1.2.3. Also set cache flush bit
+    // for the records changed.
+    private static final byte[] DATAIN_IPV4_1 = HexDump.hexStringToByteArray(
+            "0974657374686f73743100000180010000007800040a010203");
+    // MDNS record for name "testhost1" with an IPv4 address of 10.1.2.4. Also set cache flush bit
+    // for the records changed.
+    private static final byte[] DATAIN_IPV4_2 = HexDump.hexStringToByteArray(
+            "0974657374686f73743100000180010000007800040a010204");
+    // MDNS record w/name "testhost1" & IPv6 address of aabb:ccdd:1122:3344:a0b0:c0d0:1020:3040.
+    // Also set cache flush bit for the records changed.
+    private static final byte[] DATAIN_IPV6_1 = HexDump.hexStringToByteArray(
+            "0974657374686f73743100001c8001000000780010aabbccdd11223344a0b0c0d010203040");
+    // MDNS record w/name "testhost1" & IPv6 address of aabb:ccdd:1122:3344:a0b0:c0d0:1020:3030.
+    // Also set cache flush bit for the records changed.
+    private static final byte[] DATAIN_IPV6_2 = HexDump.hexStringToByteArray(
+            "0974657374686f73743100001c8001000000780010aabbccdd11223344a0b0c0d010203030");
+    // MDNS record w/name "test" & PTR to foo.bar.quxx
+    private static final byte[] DATAIN_PTR_1 = HexDump.hexStringToByteArray(
+            "047465737400000C000100001194000E03666F6F03626172047175787800");
+    // MDNS record w/name "test" & PTR to foo.bar.quxy
+    private static final byte[] DATAIN_PTR_2 = HexDump.hexStringToByteArray(
+            "047465737400000C000100001194000E03666F6F03626172047175787900");
+    // SRV record for: scapy.DNSRRSRV(rrname='foo.bar.quxx', ttl=120, port=1234, target='testhost1')
+    private static final byte[] DATAIN_SERVICE_1 = HexDump.hexStringToByteArray(
+            "03666f6f03626172047175787800002100010000007800110000000004d20974657374686f73743100");
+    // SRV record for: scapy.DNSRRSRV(rrname='foo.bar.quxx', ttl=120, port=1234, target='testhost2')
+    private static final byte[] DATAIN_SERVICE_2 = HexDump.hexStringToByteArray(
+            "03666f6f03626172047175787800002100010000007800110000000004d20974657374686f73743200");
+    // TXT record for: scapy.DNSRR(rrname='foo.bar.quxx', type='TXT', ttl=120,
+    //     rdata=[b'a=hello there', b'b=1234567890', b'xyz=!$$$'])
+    private static final byte[] DATAIN_TEXT_1 = HexDump.hexStringToByteArray(
+            "03666f6f03626172047175787800001000010000007800240d613d68656c6c6f2074686572650c623d3132"
+                    + "33343536373839300878797a3d21242424");
+
+    // TXT record for: scapy.DNSRR(rrname='foo.bar.quxx', type='TXT', ttl=120,
+    //     rdata=[b'a=hello there', b'b=1234567890', b'xyz=!$$$'])
+    private static final byte[] DATAIN_TEXT_2 = HexDump.hexStringToByteArray(
+            "03666f6f03626172047175787800001000010000007800240d613d68656c6c6f2074686572650c623d3132"
+                    + "33343536373839300878797a3d21402324");
+
+    private static final String[] DATAIN_SERVICE_NAME_1 = new String[] { "foo", "bar", "quxx" };
+
     private static final String CAST_SERVICE_NAME = "_googlecast";
     private static final String[] CAST_SERVICE_TYPE =
             new String[] {CAST_SERVICE_NAME, "_tcp", "local"};
@@ -154,41 +205,28 @@
     private static final String[] MATTER_SERVICE_TYPE =
             new String[] {MATTER_SERVICE_NAME, "_tcp", "local"};
 
-    private final List<MdnsResponse> responses = new LinkedList<>();
+    private ArraySet<MdnsResponse> responses;
 
     private final Clock mClock = mock(Clock.class);
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, CAST_SERVICE_TYPE);
         assertNotNull(data);
-        DatagramPacket packet = new DatagramPacket(data, data.length);
-        packet.setSocketAddress(
-                new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT));
-        responses.clear();
-        int errorCode = decoder.decode(
-                packet, responses, MdnsSocket.INTERFACE_INDEX_UNSPECIFIED, mock(Network.class));
-        assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
+        responses = decode(decoder, data);
         assertEquals(1, responses.size());
     }
 
     @Test
-    public void testDecodeWithNullServiceType() {
+    public void testDecodeWithNullServiceType() throws Exception {
         MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, null);
-        assertNotNull(data);
-        DatagramPacket packet = new DatagramPacket(data, data.length);
-        packet.setSocketAddress(
-                new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT));
-        responses.clear();
-        int errorCode = decoder.decode(
-                packet, responses, MdnsSocket.INTERFACE_INDEX_UNSPECIFIED, mock(Network.class));
-        assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
+        responses = decode(decoder, data);
         assertEquals(2, responses.size());
     }
 
     @Test
     public void testDecodeMultipleAnswerPacket() throws IOException {
-        MdnsResponse response = responses.get(0);
+        MdnsResponse response = responses.valueAt(0);
         assertTrue(response.isComplete());
 
         MdnsInetAddressRecord inet4AddressRecord = response.getInet4AddressRecord();
@@ -231,20 +269,9 @@
         assertEquals("st=0", textStrings.get(6));
     }
 
-    @Test
-    public void testDecodeIPv6AnswerPacket() throws IOException {
-        MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, CAST_SERVICE_TYPE);
-        assertNotNull(data6);
-        DatagramPacket packet = new DatagramPacket(data6, data6.length);
-        packet.setSocketAddress(
-                new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
-
-        responses.clear();
-        int errorCode = decoder.decode(
-                packet, responses, MdnsSocket.INTERFACE_INDEX_UNSPECIFIED, mock(Network.class));
-        assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
-
-        MdnsResponse response = responses.get(0);
+    private void verifyResponse(ArraySet<MdnsResponse> responseArraySet) {
+        assertEquals(1, responseArraySet.size());
+        MdnsResponse response = responseArraySet.valueAt(0);
         assertTrue(response.isComplete());
 
         MdnsInetAddressRecord inet6AddressRecord = response.getInet6AddressRecord();
@@ -258,105 +285,314 @@
     }
 
     @Test
+    public void testDecodeIPv6AnswerPacket() throws IOException {
+        MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, CAST_SERVICE_TYPE);
+        assertNotNull(data6);
+        verifyResponse(decode(decoder, data6));
+    }
+
+    @Test
+    public void testDecodeCaseInsensitiveMatch() throws IOException {
+        final String[] castServiceTypeUpperCase =
+                new String[] {"_GOOGLECAST", "_TCP", "LOCAL"};
+        MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, castServiceTypeUpperCase);
+        assertNotNull(data6);
+        verifyResponse(decode(decoder, data6));
+    }
+
+    @Test
     public void testIsComplete() {
-        MdnsResponse response = responses.get(0);
+        MdnsResponse response = new MdnsResponse(responses.valueAt(0));
         assertTrue(response.isComplete());
 
         response.clearPointerRecords();
+        // The service name is still known in MdnsResponse#getServiceName
+        assertTrue(response.isComplete());
+
+        response = new MdnsResponse(responses.valueAt(0));
+        response.clearInet4AddressRecords();
         assertFalse(response.isComplete());
 
-        response = responses.get(0);
-        response.setInet4AddressRecord(null);
+        response.addInet6AddressRecord(new MdnsInetAddressRecord(new String[] { "testhostname" },
+                0L /* receiptTimeMillis */, false /* cacheFlush */, 1234L /* ttlMillis */,
+                parseNumericAddress("2008:db1::123")));
+        assertTrue(response.isComplete());
+
+        response.clearInet6AddressRecords();
         assertFalse(response.isComplete());
 
-        response = responses.get(0);
-        response.setInet6AddressRecord(null);
-        assertFalse(response.isComplete());
-
-        response = responses.get(0);
+        response = new MdnsResponse(responses.valueAt(0));
         response.setServiceRecord(null);
         assertFalse(response.isComplete());
 
-        response = responses.get(0);
+        response = new MdnsResponse(responses.valueAt(0));
         response.setTextRecord(null);
         assertFalse(response.isComplete());
     }
 
     @Test
-    public void decode_withInterfaceIndex_populatesInterfaceIndex() {
+    public void decode_withInterfaceIndex_populatesInterfaceIndex() throws Exception {
         MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, CAST_SERVICE_TYPE);
         assertNotNull(data6);
         DatagramPacket packet = new DatagramPacket(data6, data6.length);
         packet.setSocketAddress(
                 new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
 
-        responses.clear();
+        final MdnsPacket parsedPacket = MdnsResponseDecoder.parseResponse(data6, data6.length);
+        assertNotNull(parsedPacket);
+
         final Network network = mock(Network.class);
-        int errorCode = decoder.decode(
-                packet, responses, /* interfaceIndex= */ 10, network);
-        assertEquals(errorCode, MdnsResponseDecoder.SUCCESS);
+        responses = decoder.augmentResponses(parsedPacket,
+                /* existingResponses= */ Collections.emptyList(),
+                /* interfaceIndex= */ 10, network /* expireOnExit= */);
+
         assertEquals(responses.size(), 1);
-        assertEquals(responses.get(0).getInterfaceIndex(), 10);
-        assertEquals(network, responses.get(0).getNetwork());
+        assertEquals(responses.valueAt(0).getInterfaceIndex(), 10);
+        assertEquals(network, responses.valueAt(0).getNetwork());
     }
 
     @Test
-    public void decode_singleHostname_multipleSrvRecords_flagEnabled_multipleCompleteResponses() {
+    public void decode_singleHostname_multipleSrvRecords_flagEnabled_multipleCompleteResponses()
+            throws Exception {
         //MdnsScannerConfigsFlagsImpl.allowMultipleSrvRecordsPerHost.override(true);
         MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, MATTER_SERVICE_TYPE);
         assertNotNull(matterDuplicateHostname);
 
-        DatagramPacket packet =
-                new DatagramPacket(matterDuplicateHostname, matterDuplicateHostname.length);
-
-        packet.setSocketAddress(
-                new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
-
-        responses.clear();
-        int errorCode = decoder.decode(
-                packet, responses, /* interfaceIndex= */ 0, mock(Network.class));
-        assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
+        responses = decode(decoder, matterDuplicateHostname);
 
         // This should emit two records:
         assertEquals(2, responses.size());
 
-        MdnsResponse response1 = responses.get(0);
-        MdnsResponse response2 = responses.get(0);
+        MdnsResponse response1 = responses.valueAt(0);
+        MdnsResponse response2 = responses.valueAt(0);
 
         // Both of which are complete:
         assertTrue(response1.isComplete());
         assertTrue(response2.isComplete());
 
         // And should both have the same IPv6 address:
-        assertEquals(InetAddresses.parseNumericAddress("2605:a601:a846:5700:3e61:5ff:fe0c:89f8"),
-                response1.getInet6AddressRecord().getInet6Address());
-        assertEquals(InetAddresses.parseNumericAddress("2605:a601:a846:5700:3e61:5ff:fe0c:89f8"),
-                response2.getInet6AddressRecord().getInet6Address());
+        assertTrue(response1.getInet6AddressRecords().stream().anyMatch(
+                record -> record.getInet6Address().equals(
+                        parseNumericAddress("2605:a601:a846:5700:3e61:5ff:fe0c:89f8"))));
+        assertTrue(response2.getInet6AddressRecords().stream().anyMatch(
+                record -> record.getInet6Address().equals(
+                        parseNumericAddress("2605:a601:a846:5700:3e61:5ff:fe0c:89f8"))));
     }
 
     @Test
     @Ignore("MdnsConfigs is not configurable currently.")
-    public void decode_singleHostname_multipleSrvRecords_flagDisabled_singleCompleteResponse() {
+    public void decode_singleHostname_multipleSrvRecords_flagDisabled_singleCompleteResponse()
+            throws Exception {
         //MdnsScannerConfigsFlagsImpl.allowMultipleSrvRecordsPerHost.override(false);
         MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, MATTER_SERVICE_TYPE);
         assertNotNull(matterDuplicateHostname);
 
-        DatagramPacket packet =
-                new DatagramPacket(matterDuplicateHostname, matterDuplicateHostname.length);
-
-        packet.setSocketAddress(
-                new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
-
-        responses.clear();
-        int errorCode = decoder.decode(
-                packet, responses, /* interfaceIndex= */ 0, mock(Network.class));
-        assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
+        responses = decode(decoder, matterDuplicateHostname);
 
         // This should emit only two records:
         assertEquals(2, responses.size());
 
         // But only the first is complete:
-        assertTrue(responses.get(0).isComplete());
-        assertFalse(responses.get(1).isComplete());
+        assertTrue(responses.valueAt(0).isComplete());
+        assertFalse(responses.valueAt(1).isComplete());
+    }
+
+    @Test
+    public void testDecodeWithIpv4AddressChange() throws IOException {
+        MdnsResponse response = makeMdnsResponse(0, DATAIN_SERVICE_NAME_1, List.of(
+                new PacketAndRecordClass(DATAIN_PTR_1,
+                        MdnsPointerRecord.class),
+                new PacketAndRecordClass(DATAIN_SERVICE_1,
+                        MdnsServiceRecord.class),
+                new PacketAndRecordClass(DATAIN_IPV4_1,
+                        MdnsInet4AddressRecord.class)));
+        // Now update the response with another address
+        final MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, null);
+        final ArraySet<MdnsResponse> updatedResponses = decode(
+                decoder, makeResponsePacket(DATAIN_IPV4_2), List.of(response));
+        assertEquals(1, updatedResponses.size());
+        assertEquals(parseNumericAddress("10.1.2.4"),
+                updatedResponses.valueAt(0).getInet4AddressRecord().getInet4Address());
+        assertEquals(parseNumericAddress("10.1.2.3"),
+                response.getInet4AddressRecord().getInet4Address());
+    }
+
+    @Test
+    public void testDecodeWithIpv6AddressChange() throws IOException {
+        MdnsResponse response = makeMdnsResponse(0, DATAIN_SERVICE_NAME_1, List.of(
+                new PacketAndRecordClass(DATAIN_PTR_1,
+                        MdnsPointerRecord.class),
+                new PacketAndRecordClass(DATAIN_SERVICE_1,
+                        MdnsServiceRecord.class),
+                new PacketAndRecordClass(DATAIN_IPV6_1,
+                        MdnsInet6AddressRecord.class)));
+        // Now update the response with another address
+        final MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, null);
+        final ArraySet<MdnsResponse> updatedResponses = decode(
+                decoder, makeResponsePacket(DATAIN_IPV6_2), List.of(response));
+        assertEquals(1, updatedResponses.size());
+        assertEquals(parseNumericAddress("aabb:ccdd:1122:3344:a0b0:c0d0:1020:3030"),
+                updatedResponses.valueAt(0).getInet6AddressRecord().getInet6Address());
+        assertEquals(parseNumericAddress("aabb:ccdd:1122:3344:a0b0:c0d0:1020:3040"),
+                response.getInet6AddressRecord().getInet6Address());
+    }
+
+    @Test
+    public void testDecodeWithChangeOnText() throws IOException {
+        MdnsResponse response = makeMdnsResponse(0, DATAIN_SERVICE_NAME_1, List.of(
+                new PacketAndRecordClass(DATAIN_PTR_1,
+                        MdnsPointerRecord.class),
+                new PacketAndRecordClass(DATAIN_SERVICE_1,
+                        MdnsServiceRecord.class),
+                new PacketAndRecordClass(DATAIN_TEXT_1,
+                        MdnsTextRecord.class)));
+        // Now update the response with another address
+        final MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, null);
+        final ArraySet<MdnsResponse> updatedResponses = decode(
+                decoder, makeResponsePacket(DATAIN_TEXT_2), List.of(response));
+        assertEquals(1, updatedResponses.size());
+        assertEquals(List.of(
+                new MdnsServiceInfo.TextEntry("a", "hello there"),
+                new MdnsServiceInfo.TextEntry("b", "1234567890"),
+                new MdnsServiceInfo.TextEntry("xyz", "!@#$")),
+                updatedResponses.valueAt(0).getTextRecord().getEntries());
+    }
+
+    @Test
+    public void testDecodeWithChangeOnService() throws IOException {
+        MdnsResponse response = makeMdnsResponse(0, DATAIN_SERVICE_NAME_1, List.of(
+                new PacketAndRecordClass(DATAIN_PTR_1,
+                        MdnsPointerRecord.class),
+                new PacketAndRecordClass(DATAIN_SERVICE_1,
+                        MdnsServiceRecord.class),
+                new PacketAndRecordClass(DATAIN_IPV4_1,
+                        MdnsInet4AddressRecord.class)));
+        assertArrayEquals(new String[] { "testhost1" },
+                response.getServiceRecord().getServiceHost());
+        assertNotNull(response.getInet4AddressRecord());
+        // Now update the response with another hostname
+        final MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, null);
+        final ArraySet<MdnsResponse> updatedResponses = decode(
+                decoder, makeResponsePacket(DATAIN_SERVICE_2), List.of(response));
+        assertEquals(1, updatedResponses.size());
+        assertArrayEquals(new String[] { "testhost2" },
+                updatedResponses.valueAt(0).getServiceRecord().getServiceHost());
+        // Hostname changed, so address records are dropped
+        assertNull(updatedResponses.valueAt(0).getInet4AddressRecord());
+    }
+
+    @Test
+    public void testDecodeWithChangeOnPtr() throws IOException {
+        MdnsResponse response = makeMdnsResponse(0, DATAIN_SERVICE_NAME_1, List.of(
+                new PacketAndRecordClass(DATAIN_PTR_1,
+                        MdnsPointerRecord.class),
+                new PacketAndRecordClass(DATAIN_SERVICE_1,
+                        MdnsServiceRecord.class)));
+        // Now update the response with another address
+        final MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, null);
+        final ArraySet<MdnsResponse> updatedResponses = decode(
+                decoder, makeResponsePacket(DATAIN_PTR_2), List.of(response));
+        assertEquals(1, updatedResponses.size());
+        assertArrayEquals(new String[] { "foo", "bar", "quxy" },
+                updatedResponses.valueAt(0).getPointerRecords().get(0).getPointer());
+    }
+
+    @Test
+    public void testDecodeWithNoChange() throws IOException {
+        List<PacketAndRecordClass> recordList =
+                Arrays.asList(
+                        new PacketAndRecordClass(DATAIN_IPV4_1, MdnsInet4AddressRecord.class),
+                        new PacketAndRecordClass(DATAIN_IPV6_1, MdnsInet6AddressRecord.class),
+                        new PacketAndRecordClass(DATAIN_PTR_1, MdnsPointerRecord.class),
+                        new PacketAndRecordClass(DATAIN_SERVICE_2, MdnsServiceRecord.class),
+                        new PacketAndRecordClass(DATAIN_TEXT_1, MdnsTextRecord.class));
+        // Create a two identical responses.
+        MdnsResponse response = makeMdnsResponse(0, DATAIN_SERVICE_NAME_1, recordList);
+
+        final MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, null);
+        final byte[] identicalResponse = makeResponsePacket(
+                recordList.stream().map(p -> p.packetData).collect(Collectors.toList()));
+        final ArraySet<MdnsResponse> changes = decode(
+                decoder, identicalResponse, List.of(response));
+
+        // Decoding should not indicate any change.
+        assertEquals(0, changes.size());
+    }
+
+    private static MdnsResponse makeMdnsResponse(long time, String[] serviceName,
+            List<PacketAndRecordClass> responseList) throws IOException {
+        final MdnsResponse response = new MdnsResponse(
+                time, serviceName, 999 /* interfaceIndex */, mock(Network.class));
+        for (PacketAndRecordClass responseData : responseList) {
+            DatagramPacket packet =
+                    new DatagramPacket(responseData.packetData, responseData.packetData.length);
+            MdnsPacketReader reader = new MdnsPacketReader(packet);
+            String[] name = reader.readLabels();
+            reader.skip(2); // skip record type indication.
+            // Apply the right kind of record to the response.
+            if (responseData.recordClass == MdnsInet4AddressRecord.class) {
+                response.addInet4AddressRecord(new MdnsInet4AddressRecord(name, reader));
+            } else if (responseData.recordClass == MdnsInet6AddressRecord.class) {
+                response.addInet6AddressRecord(new MdnsInet6AddressRecord(name, reader));
+            } else if (responseData.recordClass == MdnsPointerRecord.class) {
+                response.addPointerRecord(new MdnsPointerRecord(name, reader));
+            } else if (responseData.recordClass == MdnsServiceRecord.class) {
+                response.setServiceRecord(new MdnsServiceRecord(name, reader));
+            } else if (responseData.recordClass == MdnsTextRecord.class) {
+                response.setTextRecord(new MdnsTextRecord(name, reader));
+            } else {
+                fail("Unsupported/unexpected MdnsRecord subtype used in test - invalid test!");
+            }
+        }
+        return response;
+    }
+
+    private static byte[] makeResponsePacket(byte[] responseRecord) throws IOException {
+        return makeResponsePacket(List.of(responseRecord));
+    }
+
+    private static byte[] makeResponsePacket(List<byte[]> responseRecords) throws IOException {
+        final MdnsPacketWriter writer = new MdnsPacketWriter(1500);
+        writer.writeUInt16(0); // Transaction ID (advertisement: 0)
+        writer.writeUInt16(0x8400); // Flags: response, authoritative
+        writer.writeUInt16(0); // questions count
+        writer.writeUInt16(responseRecords.size()); // answers count
+        writer.writeUInt16(0); // authority entries count
+        writer.writeUInt16(0); // additional records count
+
+        for (byte[] record : responseRecords) {
+            writer.writeBytes(record);
+        }
+        final DatagramPacket packet = writer.getPacket(new InetSocketAddress(0 /* port */));
+        return Arrays.copyOf(packet.getData(), packet.getLength());
+    }
+
+
+    // This helper class just wraps the data bytes of a response packet with the contained record
+    // type.
+    // Its only purpose is to make the test code a bit more readable.
+    private static class PacketAndRecordClass {
+        public final byte[] packetData;
+        public final Class<?> recordClass;
+
+        PacketAndRecordClass(byte[] data, Class<?> c) {
+            packetData = data;
+            recordClass = c;
+        }
+    }
+
+    private ArraySet<MdnsResponse> decode(MdnsResponseDecoder decoder, byte[] data)
+            throws MdnsPacket.ParseException {
+        return decode(decoder, data, Collections.emptyList());
+    }
+
+    private ArraySet<MdnsResponse> decode(MdnsResponseDecoder decoder, byte[] data,
+            Collection<MdnsResponse> existingResponses) throws MdnsPacket.ParseException {
+        final MdnsPacket parsedPacket = MdnsResponseDecoder.parseResponse(data, data.length);
+        assertNotNull(parsedPacket);
+
+        return decoder.augmentResponses(parsedPacket,
+                existingResponses,
+                MdnsSocket.INTERFACE_INDEX_UNSPECIFIED, mock(Network.class));
     }
 }
\ No newline at end of file
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java
index ec57dc8..dc0e646 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java
@@ -16,6 +16,8 @@
 
 package com.android.server.connectivity.mdns;
 
+import static android.net.InetAddresses.parseNumericAddress;
+
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 
 import static org.junit.Assert.assertEquals;
@@ -23,22 +25,21 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 
+import static java.util.Collections.emptyList;
+
 import android.net.Network;
 
 import com.android.net.module.util.HexDump;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.IOException;
 import java.net.DatagramPacket;
-import java.util.Arrays;
 import java.util.List;
 
 // The record test data does not use compressed names (label pointers), since that would require
@@ -48,36 +49,24 @@
 public class MdnsResponseTests {
     private static final String TAG = "MdnsResponseTests";
     // MDNS response packet for name "test" with an IPv4 address of 10.1.2.3
-    private static final byte[] dataIn_ipv4_1 = HexDump.hexStringToByteArray(
+    private static final byte[] DATAIN_IPV4 = HexDump.hexStringToByteArray(
             "0474657374000001" + "0001000011940004" + "0A010203");
-    // MDNS response packet for name "tess" with an IPv4 address of 10.1.2.4
-    private static final byte[] dataIn_ipv4_2 = HexDump.hexStringToByteArray(
-            "0474657373000001" + "0001000011940004" + "0A010204");
     // MDNS response w/name "test" & IPv6 address of aabb:ccdd:1122:3344:a0b0:c0d0:1020:3040
-    private static final byte[] dataIn_ipv6_1 = HexDump.hexStringToByteArray(
+    private static final byte[] DATAIN_IPV6 = HexDump.hexStringToByteArray(
             "047465737400001C" + "0001000011940010" + "AABBCCDD11223344" + "A0B0C0D010203040");
-    // MDNS response w/name "test" & IPv6 address of aabb:ccdd:1122:3344:a0b0:c0d0:1020:3030
-    private static final byte[] dataIn_ipv6_2 = HexDump.hexStringToByteArray(
-            "047465737400001C" + "0001000011940010" + "AABBCCDD11223344" + "A0B0C0D010203030");
     // MDNS response w/name "test" & PTR to foo.bar.quxx
-    private static final byte[] dataIn_ptr_1 = HexDump.hexStringToByteArray(
+    private static final byte[] DATAIN_PTR = HexDump.hexStringToByteArray(
             "047465737400000C" + "000100001194000E" + "03666F6F03626172" + "047175787800");
-    // MDNS response w/name "test" & PTR to foo.bar.quxy
-    private static final byte[] dataIn_ptr_2 = HexDump.hexStringToByteArray(
-            "047465737400000C" + "000100001194000E" + "03666F6F03626172" + "047175787900");
     // MDNS response w/name "test" & Service for host foo.bar.quxx
-    private static final byte[] dataIn_service_1 = HexDump.hexStringToByteArray(
+    private static final byte[] DATAIN_SERVICE = HexDump.hexStringToByteArray(
             "0474657374000021"
             + "0001000011940014"
             + "000100FF1F480366"
             + "6F6F036261720471"
             + "75787800");
-    // MDNS response w/name "test" & Service for host test
-    private static final byte[] dataIn_service_2 = HexDump.hexStringToByteArray(
-            "0474657374000021" + "000100001194000B" + "000100FF1F480474" + "657374");
     // MDNS response w/name "test" & the following text strings:
     // "a=hello there", "b=1234567890", and "xyz=!$$$"
-    private static final byte[] dataIn_text_1 = HexDump.hexStringToByteArray(
+    private static final byte[] DATAIN_TEXT = HexDump.hexStringToByteArray(
             "0474657374000010"
             + "0001000011940024"
             + "0D613D68656C6C6F"
@@ -85,18 +74,11 @@
             + "3D31323334353637"
             + "3839300878797A3D"
             + "21242424");
-    // MDNS response w/name "test" & the following text strings:
-    // "a=hello there", "b=1234567890", and "xyz=!@#$"
-    private static final byte[] dataIn_text_2 = HexDump.hexStringToByteArray(
-            "0474657374000010"
-            + "0001000011940024"
-            + "0D613D68656C6C6F"
-            + "2074686572650C62"
-            + "3D31323334353637"
-            + "3839300878797A3D"
-            + "21402324");
+    private static final String[] TEST_SERVICE_NAME =
+            new String[] { "test", "_type", "_tcp", "local" };
 
     private static final int INTERFACE_INDEX = 999;
+    private static final int TEST_TTL_MS = 120_000;
     private final Network mNetwork = mock(Network.class);
 
     // The following helper classes act as wrappers so that IPv4 and IPv6 address records can
@@ -113,87 +95,63 @@
         }
     }
 
-    // This helper class just wraps the data bytes of a response packet with the contained record
-    // type.
-    // Its only purpose is to make the test code a bit more readable.
-    static class PacketAndRecordClass {
-        public final byte[] packetData;
-        public final Class<?> recordClass;
-
-        public PacketAndRecordClass() {
-            packetData = null;
-            recordClass = null;
-        }
-
-        public PacketAndRecordClass(byte[] data, Class<?> c) {
-            packetData = data;
-            recordClass = c;
-        }
-    }
-
-    // Construct an MdnsResponse with the specified data packets applied.
-    private MdnsResponse makeMdnsResponse(long time, List<PacketAndRecordClass> responseList)
-            throws IOException {
-        MdnsResponse response = new MdnsResponse(time, INTERFACE_INDEX, mNetwork);
-        for (PacketAndRecordClass responseData : responseList) {
-            DatagramPacket packet =
-                    new DatagramPacket(responseData.packetData, responseData.packetData.length);
-            MdnsPacketReader reader = new MdnsPacketReader(packet);
-            String[] name = reader.readLabels();
-            reader.skip(2); // skip record type indication.
-            // Apply the right kind of record to the response.
-            if (responseData.recordClass == MdnsInet4AddressRecord.class) {
-                response.setInet4AddressRecord(new MdnsInet4AddressRecord(name, reader));
-            } else if (responseData.recordClass == MdnsInet6AddressRecord.class) {
-                response.setInet6AddressRecord(new MdnsInet6AddressRecord(name, reader));
-            } else if (responseData.recordClass == MdnsPointerRecord.class) {
-                response.addPointerRecord(new MdnsPointerRecord(name, reader));
-            } else if (responseData.recordClass == MdnsServiceRecord.class) {
-                response.setServiceRecord(new MdnsServiceRecord(name, reader));
-            } else if (responseData.recordClass == MdnsTextRecord.class) {
-                response.setTextRecord(new MdnsTextRecord(name, reader));
-            } else {
-                fail("Unsupported/unexpected MdnsRecord subtype used in test - invalid test!");
-            }
-        }
+    private MdnsResponse makeCompleteResponse(int recordsTtlMillis) {
+        final String[] hostname = new String[] { "MyHostname" };
+        final String[] serviceName = new String[] { "MyService", "_type", "_tcp", "local" };
+        final String[] serviceType = new String[] { "_type", "_tcp", "local" };
+        final MdnsResponse response = new MdnsResponse(/* now= */ 0, serviceName, INTERFACE_INDEX,
+                mNetwork);
+        response.addPointerRecord(new MdnsPointerRecord(serviceType, 0L /* receiptTimeMillis */,
+                false /* cacheFlush */, recordsTtlMillis, serviceName));
+        response.setServiceRecord(new MdnsServiceRecord(serviceName, 0L /* receiptTimeMillis */,
+                true /* cacheFlush */, recordsTtlMillis, 0 /* servicePriority */,
+                0 /* serviceWeight */, 0 /* servicePort */, hostname));
+        response.setTextRecord(new MdnsTextRecord(serviceName, 0L /* receiptTimeMillis */,
+                true /* cacheFlush */, recordsTtlMillis, emptyList() /* entries */));
+        response.addInet4AddressRecord(new MdnsInetAddressRecord(
+                hostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
+                recordsTtlMillis, parseNumericAddress("192.0.2.123")));
+        response.addInet6AddressRecord(new MdnsInetAddressRecord(
+                hostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
+                recordsTtlMillis, parseNumericAddress("2001:db8::123")));
         return response;
     }
 
     @Test
     public void getInet4AddressRecord_returnsAddedRecord() throws IOException {
-        DatagramPacket packet = new DatagramPacket(dataIn_ipv4_1, dataIn_ipv4_1.length);
+        DatagramPacket packet = new DatagramPacket(DATAIN_IPV4, DATAIN_IPV4.length);
         MdnsPacketReader reader = new MdnsPacketReader(packet);
         String[] name = reader.readLabels();
         reader.skip(2); // skip record type indication.
         MdnsInetAddressRecord record = new MdnsInetAddressRecord(name, MdnsRecord.TYPE_A, reader);
-        MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
+        MdnsResponse response = new MdnsResponse(0, TEST_SERVICE_NAME, INTERFACE_INDEX, mNetwork);
         assertFalse(response.hasInet4AddressRecord());
-        assertTrue(response.setInet4AddressRecord(record));
+        assertTrue(response.addInet4AddressRecord(record));
         assertEquals(response.getInet4AddressRecord(), record);
     }
 
     @Test
     public void getInet6AddressRecord_returnsAddedRecord() throws IOException {
-        DatagramPacket packet = new DatagramPacket(dataIn_ipv6_1, dataIn_ipv6_1.length);
+        DatagramPacket packet = new DatagramPacket(DATAIN_IPV6, DATAIN_IPV6.length);
         MdnsPacketReader reader = new MdnsPacketReader(packet);
         String[] name = reader.readLabels();
         reader.skip(2); // skip record type indication.
         MdnsInetAddressRecord record =
                 new MdnsInetAddressRecord(name, MdnsRecord.TYPE_AAAA, reader);
-        MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
+        MdnsResponse response = new MdnsResponse(0, TEST_SERVICE_NAME, INTERFACE_INDEX, mNetwork);
         assertFalse(response.hasInet6AddressRecord());
-        assertTrue(response.setInet6AddressRecord(record));
+        assertTrue(response.addInet6AddressRecord(record));
         assertEquals(response.getInet6AddressRecord(), record);
     }
 
     @Test
     public void getPointerRecords_returnsAddedRecord() throws IOException {
-        DatagramPacket packet = new DatagramPacket(dataIn_ptr_1, dataIn_ptr_1.length);
+        DatagramPacket packet = new DatagramPacket(DATAIN_PTR, DATAIN_PTR.length);
         MdnsPacketReader reader = new MdnsPacketReader(packet);
         String[] name = reader.readLabels();
         reader.skip(2); // skip record type indication.
         MdnsPointerRecord record = new MdnsPointerRecord(name, reader);
-        MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
+        MdnsResponse response = new MdnsResponse(0, record.getPointer(), INTERFACE_INDEX, mNetwork);
         assertFalse(response.hasPointerRecords());
         assertTrue(response.addPointerRecord(record));
         List<MdnsPointerRecord> recordList = response.getPointerRecords();
@@ -204,12 +162,12 @@
 
     @Test
     public void getServiceRecord_returnsAddedRecord() throws IOException {
-        DatagramPacket packet = new DatagramPacket(dataIn_service_1, dataIn_service_1.length);
+        DatagramPacket packet = new DatagramPacket(DATAIN_SERVICE, DATAIN_SERVICE.length);
         MdnsPacketReader reader = new MdnsPacketReader(packet);
         String[] name = reader.readLabels();
         reader.skip(2); // skip record type indication.
         MdnsServiceRecord record = new MdnsServiceRecord(name, reader);
-        MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
+        MdnsResponse response = new MdnsResponse(0, name, INTERFACE_INDEX, mNetwork);
         assertFalse(response.hasServiceRecord());
         assertTrue(response.setServiceRecord(record));
         assertEquals(response.getServiceRecord(), record);
@@ -217,12 +175,12 @@
 
     @Test
     public void getTextRecord_returnsAddedRecord() throws IOException {
-        DatagramPacket packet = new DatagramPacket(dataIn_text_1, dataIn_text_1.length);
+        DatagramPacket packet = new DatagramPacket(DATAIN_TEXT, DATAIN_TEXT.length);
         MdnsPacketReader reader = new MdnsPacketReader(packet);
         String[] name = reader.readLabels();
         reader.skip(2); // skip record type indication.
         MdnsTextRecord record = new MdnsTextRecord(name, reader);
-        MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
+        MdnsResponse response = new MdnsResponse(0, name, INTERFACE_INDEX, mNetwork);
         assertFalse(response.hasTextRecord());
         assertTrue(response.setTextRecord(record));
         assertEquals(response.getTextRecord(), record);
@@ -230,104 +188,118 @@
 
     @Test
     public void getInterfaceIndex() {
-        final MdnsResponse response1 = new MdnsResponse(/* now= */ 0, INTERFACE_INDEX, mNetwork);
+        final MdnsResponse response1 = new MdnsResponse(/* now= */ 0, TEST_SERVICE_NAME,
+                INTERFACE_INDEX, mNetwork);
         assertEquals(INTERFACE_INDEX, response1.getInterfaceIndex());
 
-        final MdnsResponse response2 =
-                new MdnsResponse(/* now= */ 0, 1234 /* interfaceIndex */, mNetwork);
+        final MdnsResponse response2 = new MdnsResponse(/* now= */ 0, TEST_SERVICE_NAME,
+                1234 /* interfaceIndex */, mNetwork);
         assertEquals(1234, response2.getInterfaceIndex());
     }
 
     @Test
     public void testGetNetwork() {
-        final MdnsResponse response1 =
-                new MdnsResponse(/* now= */ 0, INTERFACE_INDEX, null /* network */);
+        final MdnsResponse response1 = new MdnsResponse(/* now= */ 0, TEST_SERVICE_NAME,
+                INTERFACE_INDEX, null /* network */);
         assertNull(response1.getNetwork());
 
-        final MdnsResponse response2 =
-                new MdnsResponse(/* now= */ 0, 1234 /* interfaceIndex */, mNetwork);
+        final MdnsResponse response2 = new MdnsResponse(/* now= */ 0, TEST_SERVICE_NAME,
+                1234 /* interfaceIndex */, mNetwork);
         assertEquals(mNetwork, response2.getNetwork());
     }
 
     @Test
-    public void mergeRecordsFrom_indicates_change_on_ipv4_address() throws IOException {
-        MdnsResponse response = makeMdnsResponse(
-                0,
-                Arrays.asList(
-                        new PacketAndRecordClass(dataIn_ipv4_1, MdnsInet4AddressRecord.class)));
-        // Now create a new response that updates the address.
-        MdnsResponse response2 = makeMdnsResponse(
-                100,
-                Arrays.asList(
-                        new PacketAndRecordClass(dataIn_ipv4_2, MdnsInet4AddressRecord.class)));
-        assertTrue(response.mergeRecordsFrom(response2));
+    public void copyConstructor() {
+        final MdnsResponse response = makeCompleteResponse(TEST_TTL_MS);
+        final MdnsResponse copy = new MdnsResponse(response);
+
+        assertEquals(response.getInet6AddressRecord(), copy.getInet6AddressRecord());
+        assertEquals(response.getInet4AddressRecord(), copy.getInet4AddressRecord());
+        assertEquals(response.getPointerRecords(), copy.getPointerRecords());
+        assertEquals(response.getServiceRecord(), copy.getServiceRecord());
+        assertEquals(response.getTextRecord(), copy.getTextRecord());
+        assertEquals(response.getRecords(), copy.getRecords());
+        assertEquals(response.getNetwork(), copy.getNetwork());
+        assertEquals(response.getInterfaceIndex(), copy.getInterfaceIndex());
     }
 
     @Test
-    public void mergeRecordsFrom_indicates_change_on_ipv6_address() throws IOException {
-        MdnsResponse response = makeMdnsResponse(
-                0,
-                Arrays.asList(
-                        new PacketAndRecordClass(dataIn_ipv6_1, MdnsInet6AddressRecord.class)));
-        // Now create a new response that updates the address.
-        MdnsResponse response2 = makeMdnsResponse(
-                100,
-                Arrays.asList(
-                        new PacketAndRecordClass(dataIn_ipv6_2, MdnsInet6AddressRecord.class)));
-        assertTrue(response.mergeRecordsFrom(response2));
+    public void addRecords_noChange() {
+        final MdnsResponse response = makeCompleteResponse(TEST_TTL_MS);
+
+        assertFalse(response.addPointerRecord(response.getPointerRecords().get(0)));
+        final String[] serviceName = new String[] { "MYSERVICE", "_TYPE", "_tcp", "local" };
+        final String[] serviceType = new String[] { "_TYPE", "_tcp", "local" };
+        MdnsPointerRecord pointerRecordCaseInsensitive =
+                new MdnsPointerRecord(serviceType, 0L /* receiptTimeMillis */,
+                        false /* cacheFlush */, TEST_TTL_MS, serviceName);
+        assertFalse(response.addPointerRecord(pointerRecordCaseInsensitive));
+        assertFalse(response.addInet6AddressRecord(response.getInet6AddressRecord()));
+        assertFalse(response.addInet4AddressRecord(response.getInet4AddressRecord()));
+        assertFalse(response.setServiceRecord(response.getServiceRecord()));
+        assertFalse(response.setTextRecord(response.getTextRecord()));
     }
 
     @Test
-    public void mergeRecordsFrom_indicates_change_on_text() throws IOException {
-        MdnsResponse response = makeMdnsResponse(
-                0,
-                Arrays.asList(new PacketAndRecordClass(dataIn_text_1, MdnsTextRecord.class)));
-        // Now create a new response that updates the address.
-        MdnsResponse response2 = makeMdnsResponse(
-                100,
-                Arrays.asList(new PacketAndRecordClass(dataIn_text_2, MdnsTextRecord.class)));
-        assertTrue(response.mergeRecordsFrom(response2));
+    public void addRecords_ttlChange() {
+        final MdnsResponse response = makeCompleteResponse(TEST_TTL_MS);
+        final MdnsResponse ttlZeroResponse = makeCompleteResponse(0);
+
+        assertTrue(response.addPointerRecord(ttlZeroResponse.getPointerRecords().get(0)));
+        assertEquals(1, response.getPointerRecords().size());
+        assertEquals(0, response.getPointerRecords().get(0).getTtl());
+        assertTrue(response.getRecords().stream().anyMatch(r ->
+                r == response.getPointerRecords().get(0)));
+
+        assertTrue(response.addInet6AddressRecord(ttlZeroResponse.getInet6AddressRecord()));
+        assertEquals(1, response.getInet6AddressRecords().size());
+        assertEquals(0, response.getInet6AddressRecord().getTtl());
+        assertTrue(response.getRecords().stream().anyMatch(r ->
+                r == response.getInet6AddressRecord()));
+
+        assertTrue(response.addInet4AddressRecord(ttlZeroResponse.getInet4AddressRecord()));
+        assertEquals(1, response.getInet4AddressRecords().size());
+        assertEquals(0, response.getInet4AddressRecord().getTtl());
+        assertTrue(response.getRecords().stream().anyMatch(r ->
+                r == response.getInet4AddressRecord()));
+
+        assertTrue(response.setServiceRecord(ttlZeroResponse.getServiceRecord()));
+        assertEquals(0, response.getServiceRecord().getTtl());
+        assertTrue(response.getRecords().stream().anyMatch(r ->
+                r == response.getServiceRecord()));
+
+        assertTrue(response.setTextRecord(ttlZeroResponse.getTextRecord()));
+        assertEquals(0, response.getTextRecord().getTtl());
+        assertTrue(response.getRecords().stream().anyMatch(r ->
+                r == response.getTextRecord()));
+
+        // All records were replaced, not added
+        assertEquals(ttlZeroResponse.getRecords().size(), response.getRecords().size());
     }
 
     @Test
-    public void mergeRecordsFrom_indicates_change_on_service() throws IOException {
-        MdnsResponse response = makeMdnsResponse(
-                0,
-                Arrays.asList(new PacketAndRecordClass(dataIn_service_1, MdnsServiceRecord.class)));
-        // Now create a new response that updates the address.
-        MdnsResponse response2 = makeMdnsResponse(
-                100,
-                Arrays.asList(new PacketAndRecordClass(dataIn_service_2, MdnsServiceRecord.class)));
-        assertTrue(response.mergeRecordsFrom(response2));
-    }
+    public void dropUnmatchedAddressRecords_caseInsensitive() {
 
-    @Test
-    public void mergeRecordsFrom_indicates_change_on_pointer() throws IOException {
-        MdnsResponse response = makeMdnsResponse(
-                0,
-                Arrays.asList(new PacketAndRecordClass(dataIn_ptr_1, MdnsPointerRecord.class)));
-        // Now create a new response that updates the address.
-        MdnsResponse response2 = makeMdnsResponse(
-                100,
-                Arrays.asList(new PacketAndRecordClass(dataIn_ptr_2, MdnsPointerRecord.class)));
-        assertTrue(response.mergeRecordsFrom(response2));
-    }
+        final String[] hostname = new String[] { "MyHostname" };
+        final String[] upperCaseHostName = new String[] { "MYHOSTNAME" };
+        final String[] serviceName = new String[] { "MyService", "_type", "_tcp", "local" };
+        final String[] serviceType = new String[] { "_type", "_tcp", "local" };
+        final MdnsResponse response = new MdnsResponse(/* now= */ 0, serviceName, INTERFACE_INDEX,
+                mNetwork);
+        response.addPointerRecord(new MdnsPointerRecord(serviceType, 0L /* receiptTimeMillis */,
+                false /* cacheFlush */, TEST_TTL_MS, serviceName));
+        response.setServiceRecord(new MdnsServiceRecord(serviceName, 0L /* receiptTimeMillis */,
+                true /* cacheFlush */, TEST_TTL_MS, 0 /* servicePriority */,
+                0 /* serviceWeight */, 0 /* servicePort */, hostname));
+        response.setTextRecord(new MdnsTextRecord(serviceName, 0L /* receiptTimeMillis */,
+                true /* cacheFlush */, TEST_TTL_MS, emptyList() /* entries */));
+        response.addInet4AddressRecord(new MdnsInetAddressRecord(
+                upperCaseHostName , 0L /* receiptTimeMillis */, true /* cacheFlush */,
+                TEST_TTL_MS, parseNumericAddress("192.0.2.123")));
+        response.addInet6AddressRecord(new MdnsInetAddressRecord(
+                upperCaseHostName, 0L /* receiptTimeMillis */, true /* cacheFlush */,
+                TEST_TTL_MS, parseNumericAddress("2001:db8::123")));
 
-    @Test
-    @Ignore("MdnsConfigs is not configurable currently.")
-    public void mergeRecordsFrom_indicates_noChange() throws IOException {
-        //MdnsConfigsFlagsImpl.useReducedMergeRecordUpdateEvents.override(true);
-        List<PacketAndRecordClass> recordList =
-                Arrays.asList(
-                        new PacketAndRecordClass(dataIn_ipv4_1, MdnsInet4AddressRecord.class),
-                        new PacketAndRecordClass(dataIn_ipv6_1, MdnsInet6AddressRecord.class),
-                        new PacketAndRecordClass(dataIn_ptr_1, MdnsPointerRecord.class),
-                        new PacketAndRecordClass(dataIn_service_2, MdnsServiceRecord.class),
-                        new PacketAndRecordClass(dataIn_text_1, MdnsTextRecord.class));
-        // Create a two identical responses.
-        MdnsResponse response = makeMdnsResponse(0, recordList);
-        MdnsResponse response2 = makeMdnsResponse(100, recordList);
-        // Merging should not indicate any change.
-        assertFalse(response.mergeRecordsFrom(response2));
+        assertFalse(response.dropUnmatchedAddressRecords());
     }
 }
\ No newline at end of file
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
new file mode 100644
index 0000000..f091eea
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns
+
+import android.net.Network
+import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import kotlin.test.assertNotNull
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+
+private const val SERVICE_NAME_1 = "service-instance-1"
+private const val SERVICE_NAME_2 = "service-instance-2"
+private const val SERVICE_TYPE_1 = "_test1._tcp.local"
+private const val SERVICE_TYPE_2 = "_test2._tcp.local"
+private const val INTERFACE_INDEX = 999
+private const val DEFAULT_TIMEOUT_MS = 2000L
+
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class MdnsServiceCacheTest {
+    private val network = mock(Network::class.java)
+    private val thread = HandlerThread(MdnsServiceCacheTest::class.simpleName)
+    private val handler by lazy {
+        Handler(thread.looper)
+    }
+    private val serviceCache by lazy {
+        MdnsServiceCache(thread.looper)
+    }
+
+    @Before
+    fun setUp() {
+        thread.start()
+    }
+
+    @After
+    fun tearDown() {
+        thread.quitSafely()
+    }
+
+    private fun <T> runningOnHandlerAndReturn(functor: (() -> T)): T {
+        val future = CompletableFuture<T>()
+        handler.post {
+            future.complete(functor())
+        }
+        return future.get(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
+    }
+
+    private fun addOrUpdateService(serviceType: String, network: Network, service: MdnsResponse):
+            Unit = runningOnHandlerAndReturn {
+        serviceCache.addOrUpdateService(serviceType, network, service) }
+
+    private fun removeService(serviceName: String, serviceType: String, network: Network):
+            Unit = runningOnHandlerAndReturn {
+        serviceCache.removeService(serviceName, serviceType, network) }
+
+    private fun getService(serviceName: String, serviceType: String, network: Network):
+            MdnsResponse? = runningOnHandlerAndReturn {
+        serviceCache.getCachedService(serviceName, serviceType, network) }
+
+    private fun getServices(serviceType: String, network: Network): List<MdnsResponse> =
+        runningOnHandlerAndReturn { serviceCache.getCachedServices(serviceType, network) }
+
+    @Test
+    fun testAddAndRemoveService() {
+        addOrUpdateService(SERVICE_TYPE_1, network, createResponse(SERVICE_NAME_1, SERVICE_TYPE_1))
+        var response = getService(SERVICE_NAME_1, SERVICE_TYPE_1, network)
+        assertNotNull(response)
+        assertEquals(SERVICE_NAME_1, response.serviceInstanceName)
+        removeService(SERVICE_NAME_1, SERVICE_TYPE_1, network)
+        response = getService(SERVICE_NAME_1, SERVICE_TYPE_1, network)
+        assertNull(response)
+    }
+
+    @Test
+    fun testGetCachedServices_multipleServiceTypes() {
+        addOrUpdateService(SERVICE_TYPE_1, network, createResponse(SERVICE_NAME_1, SERVICE_TYPE_1))
+        addOrUpdateService(SERVICE_TYPE_1, network, createResponse(SERVICE_NAME_2, SERVICE_TYPE_1))
+        addOrUpdateService(SERVICE_TYPE_2, network, createResponse(SERVICE_NAME_2, SERVICE_TYPE_2))
+
+        val responses1 = getServices(SERVICE_TYPE_1, network)
+        assertEquals(2, responses1.size)
+        assertTrue(responses1.stream().anyMatch { response ->
+            response.serviceInstanceName == SERVICE_NAME_1
+        })
+        assertTrue(responses1.any { response ->
+            response.serviceInstanceName == SERVICE_NAME_2
+        })
+        val responses2 = getServices(SERVICE_TYPE_2, network)
+        assertEquals(1, responses2.size)
+        assertTrue(responses2.any { response ->
+            response.serviceInstanceName == SERVICE_NAME_2
+        })
+
+        removeService(SERVICE_NAME_2, SERVICE_TYPE_1, network)
+        val responses3 = getServices(SERVICE_TYPE_1, network)
+        assertEquals(1, responses3.size)
+        assertTrue(responses3.any { response ->
+            response.serviceInstanceName == SERVICE_NAME_1
+        })
+        val responses4 = getServices(SERVICE_TYPE_2, network)
+        assertEquals(1, responses4.size)
+        assertTrue(responses4.any { response ->
+            response.serviceInstanceName == SERVICE_NAME_2
+        })
+    }
+
+    private fun createResponse(serviceInstanceName: String, serviceType: String) = MdnsResponse(
+        0 /* now */, "$serviceInstanceName.$serviceType".split(".").toTypedArray(),
+            INTERFACE_INDEX, network)
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
index 76728cf..e7d7a98 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
@@ -119,6 +119,26 @@
     }
 
     @Test
+    public void constructor_createWithUppercaseKeys_correctAttributes() {
+        MdnsServiceInfo info =
+                new MdnsServiceInfo(
+                        "my-mdns-service",
+                        new String[] {"_testtype", "_tcp"},
+                        List.of(),
+                        new String[] {"my-host", "local"},
+                        12345,
+                        "192.168.1.1",
+                        "2001::1",
+                        List.of("KEY=Value"),
+                        /* textEntries= */ null);
+
+        assertEquals("Value", info.getAttributeByKey("key"));
+        assertEquals("Value", info.getAttributeByKey("KEY"));
+        assertEquals(1, info.getAttributes().size());
+        assertEquals("KEY", info.getAttributes().keySet().iterator().next());
+    }
+
+    @Test
     public void getInterfaceIndex_constructorWithDefaultValues_returnsMinusOne() {
         MdnsServiceInfo info =
                 new MdnsServiceInfo(
@@ -177,8 +197,8 @@
                         List.of(),
                         new String[] {"my-host", "local"},
                         12345,
-                        "192.168.1.1",
-                        "2001::1",
+                        List.of("192.168.1.1"),
+                        List.of("2001::1"),
                         List.of(),
                         /* textEntries= */ null,
                         /* interfaceIndex= */ 20,
@@ -197,8 +217,8 @@
                         List.of(),
                         new String[] {"my-host", "local"},
                         12345,
-                        "192.168.1.1",
-                        "2001::1",
+                        List.of("192.168.1.1", "192.168.1.2"),
+                        List.of("2001::1", "2001::2"),
                         List.of("vn=Alphabet Inc.", "mn=Google Nest Hub Max", "id=12345"),
                         List.of(
                                 MdnsServiceInfo.TextEntry.fromString("vn=Google Inc."),
@@ -217,7 +237,9 @@
         assertArrayEquals(beforeParcel.getHostName(), afterParcel.getHostName());
         assertEquals(beforeParcel.getPort(), afterParcel.getPort());
         assertEquals(beforeParcel.getIpv4Address(), afterParcel.getIpv4Address());
+        assertEquals(beforeParcel.getIpv4Addresses(), afterParcel.getIpv4Addresses());
         assertEquals(beforeParcel.getIpv6Address(), afterParcel.getIpv6Address());
+        assertEquals(beforeParcel.getIpv6Addresses(), afterParcel.getIpv6Addresses());
         assertEquals(beforeParcel.getAttributes(), afterParcel.getAttributes());
         assertEquals(beforeParcel.getInterfaceIndex(), afterParcel.getInterfaceIndex());
         assertEquals(beforeParcel.getNetwork(), afterParcel.getNetwork());
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index 697116c..f15e8ff 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -21,16 +21,16 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -42,6 +42,8 @@
 import android.net.Network;
 import android.text.TextUtils;
 
+import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.SharedLog;
 import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
 import com.android.server.connectivity.mdns.MdnsServiceTypeClient.QueryTaskConfig;
 import com.android.testutils.DevSdkIgnoreRule;
@@ -52,6 +54,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
 import org.mockito.Captor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
@@ -60,9 +63,8 @@
 
 import java.io.IOException;
 import java.net.DatagramPacket;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.SocketAddress;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -72,6 +74,7 @@
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
 
 /** Tests for {@link MdnsServiceTypeClient}. */
 @RunWith(DevSdkIgnoreRunner.class)
@@ -80,7 +83,14 @@
     private static final int INTERFACE_INDEX = 999;
     private static final String SERVICE_TYPE = "_googlecast._tcp.local";
     private static final String[] SERVICE_TYPE_LABELS = TextUtils.split(SERVICE_TYPE, "\\.");
-    private static final Network NETWORK = mock(Network.class);
+    private static final InetSocketAddress IPV4_ADDRESS = new InetSocketAddress(
+            MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT);
+    private static final InetSocketAddress IPV6_ADDRESS = new InetSocketAddress(
+            MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT);
+
+    private static final long TEST_TTL = 120000L;
+    private static final long TEST_ELAPSED_REALTIME = 123L;
+    private static final long TEST_TIMEOUT_MS = 10_000L;
 
     @Mock
     private MdnsServiceBrowserListener mockListenerOne;
@@ -89,13 +99,20 @@
     @Mock
     private MdnsPacketWriter mockPacketWriter;
     @Mock
-    private MdnsSocketClient mockSocketClient;
+    private MdnsMultinetworkSocketClient mockSocketClient;
+    @Mock
+    private Network mockNetwork;
+    @Mock
+    private MdnsResponseDecoder.Clock mockDecoderClock;
+    @Mock
+    private SharedLog mockSharedLog;
     @Captor
     private ArgumentCaptor<MdnsServiceInfo> serviceInfoCaptor;
 
     private final byte[] buf = new byte[10];
 
-    private DatagramPacket[] expectedPackets;
+    private DatagramPacket[] expectedIPv4Packets;
+    private DatagramPacket[] expectedIPv6Packets;
     private ScheduledFuture<?>[] expectedSendFutures;
     private FakeExecutor currentThreadExecutor = new FakeExecutor();
 
@@ -105,34 +122,58 @@
     @SuppressWarnings("DoNotMock")
     public void setUp() throws IOException {
         MockitoAnnotations.initMocks(this);
+        doReturn(TEST_ELAPSED_REALTIME).when(mockDecoderClock).elapsedRealtime();
 
-        expectedPackets = new DatagramPacket[16];
+        expectedIPv4Packets = new DatagramPacket[16];
+        expectedIPv6Packets = new DatagramPacket[16];
         expectedSendFutures = new ScheduledFuture<?>[16];
 
         for (int i = 0; i < expectedSendFutures.length; ++i) {
-            expectedPackets[i] = new DatagramPacket(buf, 0, 5);
+            expectedIPv4Packets[i] = new DatagramPacket(buf, 0 /* offset */, 5 /* length */,
+                    MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT);
+            expectedIPv6Packets[i] = new DatagramPacket(buf, 0 /* offset */, 5 /* length */,
+                    MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT);
             expectedSendFutures[i] = Mockito.mock(ScheduledFuture.class);
         }
-        when(mockPacketWriter.getPacket(any(SocketAddress.class)))
-                .thenReturn(expectedPackets[0])
-                .thenReturn(expectedPackets[1])
-                .thenReturn(expectedPackets[2])
-                .thenReturn(expectedPackets[3])
-                .thenReturn(expectedPackets[4])
-                .thenReturn(expectedPackets[5])
-                .thenReturn(expectedPackets[6])
-                .thenReturn(expectedPackets[7])
-                .thenReturn(expectedPackets[8])
-                .thenReturn(expectedPackets[9])
-                .thenReturn(expectedPackets[10])
-                .thenReturn(expectedPackets[11])
-                .thenReturn(expectedPackets[12])
-                .thenReturn(expectedPackets[13])
-                .thenReturn(expectedPackets[14])
-                .thenReturn(expectedPackets[15]);
+        when(mockPacketWriter.getPacket(IPV4_ADDRESS))
+                .thenReturn(expectedIPv4Packets[0])
+                .thenReturn(expectedIPv4Packets[1])
+                .thenReturn(expectedIPv4Packets[2])
+                .thenReturn(expectedIPv4Packets[3])
+                .thenReturn(expectedIPv4Packets[4])
+                .thenReturn(expectedIPv4Packets[5])
+                .thenReturn(expectedIPv4Packets[6])
+                .thenReturn(expectedIPv4Packets[7])
+                .thenReturn(expectedIPv4Packets[8])
+                .thenReturn(expectedIPv4Packets[9])
+                .thenReturn(expectedIPv4Packets[10])
+                .thenReturn(expectedIPv4Packets[11])
+                .thenReturn(expectedIPv4Packets[12])
+                .thenReturn(expectedIPv4Packets[13])
+                .thenReturn(expectedIPv4Packets[14])
+                .thenReturn(expectedIPv4Packets[15]);
+
+        when(mockPacketWriter.getPacket(IPV6_ADDRESS))
+                .thenReturn(expectedIPv6Packets[0])
+                .thenReturn(expectedIPv6Packets[1])
+                .thenReturn(expectedIPv6Packets[2])
+                .thenReturn(expectedIPv6Packets[3])
+                .thenReturn(expectedIPv6Packets[4])
+                .thenReturn(expectedIPv6Packets[5])
+                .thenReturn(expectedIPv6Packets[6])
+                .thenReturn(expectedIPv6Packets[7])
+                .thenReturn(expectedIPv6Packets[8])
+                .thenReturn(expectedIPv6Packets[9])
+                .thenReturn(expectedIPv6Packets[10])
+                .thenReturn(expectedIPv6Packets[11])
+                .thenReturn(expectedIPv6Packets[12])
+                .thenReturn(expectedIPv6Packets[13])
+                .thenReturn(expectedIPv6Packets[14])
+                .thenReturn(expectedIPv6Packets[15]);
 
         client =
-                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor) {
+                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
+                        mockDecoderClock, mockNetwork, mockSharedLog) {
                     @Override
                     MdnsPacketWriter createMdnsPacketWriter() {
                         return mockPacketWriter;
@@ -282,8 +323,8 @@
         //MdnsConfigsFlagsImpl.alwaysAskForUnicastResponseInEachBurst.override(true);
         MdnsSearchOptions searchOptions =
                 MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(false).build();
-        QueryTaskConfig config =
-                new QueryTaskConfig(searchOptions.getSubtypes(), searchOptions.isPassiveMode(), 1);
+        QueryTaskConfig config = new QueryTaskConfig(
+                searchOptions.getSubtypes(), searchOptions.isPassiveMode(), 1, mockNetwork);
 
         // This is the first query. We will ask for unicast response.
         assertTrue(config.expectUnicastResponse);
@@ -311,8 +352,8 @@
     public void testQueryTaskConfig_askForUnicastInFirstQuery() {
         MdnsSearchOptions searchOptions =
                 MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(false).build();
-        QueryTaskConfig config =
-                new QueryTaskConfig(searchOptions.getSubtypes(), searchOptions.isPassiveMode(), 1);
+        QueryTaskConfig config = new QueryTaskConfig(
+                searchOptions.getSubtypes(), searchOptions.isPassiveMode(), 1, mockNetwork);
 
         // This is the first query. We will ask for unicast response.
         assertTrue(config.expectUnicastResponse);
@@ -385,17 +426,46 @@
         assertNull(currentThreadExecutor.getAndClearLastScheduledRunnable());
     }
 
+    @Test
+    public void testQueryScheduledWhenAnsweredFromCache() {
+        final MdnsSearchOptions searchOptions = MdnsSearchOptions.getDefaultOptions();
+        client.startSendAndReceive(mockListenerOne, searchOptions);
+        assertNotNull(currentThreadExecutor.getAndClearSubmittedRunnable());
+
+        client.processResponse(createResponse(
+                "service-instance-1", "192.0.2.123", 5353,
+                SERVICE_TYPE_LABELS,
+                Collections.emptyMap(), TEST_TTL), /* interfaceIndex= */ 20, mockNetwork);
+
+        verify(mockListenerOne).onServiceNameDiscovered(any());
+        verify(mockListenerOne).onServiceFound(any());
+
+        // File another identical query
+        client.startSendAndReceive(mockListenerTwo, searchOptions);
+
+        verify(mockListenerTwo).onServiceNameDiscovered(any());
+        verify(mockListenerTwo).onServiceFound(any());
+
+        // This time no query is submitted, only scheduled
+        assertNull(currentThreadExecutor.getAndClearSubmittedRunnable());
+        assertNotNull(currentThreadExecutor.getAndClearLastScheduledRunnable());
+        // This just skips the first query of the first burst
+        assertEquals(MdnsConfigs.timeBetweenQueriesInBurstMs(),
+                currentThreadExecutor.getAndClearLastScheduledDelayInMs());
+    }
+
     private static void verifyServiceInfo(MdnsServiceInfo serviceInfo, String serviceName,
-            String[] serviceType, String ipv4Address, String ipv6Address, int port,
+            String[] serviceType, List<String> ipv4Addresses, List<String> ipv6Addresses, int port,
             List<String> subTypes, Map<String, String> attributes, int interfaceIndex,
             Network network) {
         assertEquals(serviceName, serviceInfo.getServiceInstanceName());
         assertArrayEquals(serviceType, serviceInfo.getServiceType());
-        assertEquals(ipv4Address, serviceInfo.getIpv4Address());
-        assertEquals(ipv6Address, serviceInfo.getIpv6Address());
+        assertEquals(ipv4Addresses, serviceInfo.getIpv4Addresses());
+        assertEquals(ipv6Addresses, serviceInfo.getIpv6Addresses());
         assertEquals(port, serviceInfo.getPort());
         assertEquals(subTypes, serviceInfo.getSubtypes());
         for (String key : attributes.keySet()) {
+            assertTrue(attributes.containsKey(key));
             assertEquals(attributes.get(key), serviceInfo.getAttributeByKey(key));
         }
         assertEquals(interfaceIndex, serviceInfo.getInterfaceIndex());
@@ -406,24 +476,21 @@
     public void processResponse_incompleteResponse() {
         client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
 
-        MdnsResponse response = mock(MdnsResponse.class);
-        when(response.getServiceInstanceName()).thenReturn("service-instance-1");
-        doReturn(INTERFACE_INDEX).when(response).getInterfaceIndex();
-        doReturn(NETWORK).when(response).getNetwork();
-        when(response.isComplete()).thenReturn(false);
-
-        client.processResponse(response);
+        client.processResponse(createResponse(
+                "service-instance-1", null /* host */, 0 /* port */,
+                SERVICE_TYPE_LABELS,
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
         verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
                 "service-instance-1",
                 SERVICE_TYPE_LABELS,
-                null /* ipv4Address */,
-                null /* ipv6Address */,
-                0 /* port */,
-                List.of() /* subTypes */,
-                Collections.singletonMap("key", null) /* attributes */,
+                /* ipv4Addresses= */ List.of(),
+                /* ipv6Addresses= */ List.of(),
+                /* port= */ 0,
+                /* subTypes= */ List.of(),
+                Collections.emptyMap(),
                 INTERFACE_INDEX,
-                NETWORK);
+                mockNetwork);
 
         verify(mockListenerOne, never()).onServiceFound(any(MdnsServiceInfo.class));
         verify(mockListenerOne, never()).onServiceUpdated(any(MdnsServiceInfo.class));
@@ -435,41 +502,30 @@
         client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
 
         // Process the initial response.
-        MdnsResponse initialResponse =
-                createResponse(
-                        "service-instance-1",
-                        ipV4Address,
-                        5353,
-                        /* subtype= */ "ABCDE",
-                        Collections.emptyMap(),
-                        /* interfaceIndex= */ 20,
-                        NETWORK);
-        client.processResponse(initialResponse);
+        client.processResponse(createResponse(
+                "service-instance-1", ipV4Address, 5353,
+                /* subtype= */ "ABCDE",
+                Collections.emptyMap(), TEST_TTL), /* interfaceIndex= */ 20, mockNetwork);
 
         // Process a second response with a different port and updated text attributes.
-        MdnsResponse secondResponse =
-                createResponse(
-                        "service-instance-1",
-                        ipV4Address,
-                        5354,
-                        /* subtype= */ "ABCDE",
-                        Collections.singletonMap("key", "value"),
-                        /* interfaceIndex= */ 20,
-                        NETWORK);
-        client.processResponse(secondResponse);
+        client.processResponse(createResponse(
+                "service-instance-1", ipV4Address, 5354,
+                /* subtype= */ "ABCDE",
+                Collections.singletonMap("key", "value"), TEST_TTL),
+                /* interfaceIndex= */ 20, mockNetwork);
 
         // Verify onServiceNameDiscovered was called once for the initial response.
         verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
                 "service-instance-1",
                 SERVICE_TYPE_LABELS,
-                ipV4Address /* ipv4Address */,
-                null /* ipv6Address */,
+                List.of(ipV4Address) /* ipv4Address */,
+                List.of() /* ipv6Address */,
                 5353 /* port */,
                 Collections.singletonList("ABCDE") /* subTypes */,
                 Collections.singletonMap("key", null) /* attributes */,
                 20 /* interfaceIndex */,
-                NETWORK);
+                mockNetwork);
 
         // Verify onServiceFound was called once for the initial response.
         verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
@@ -480,7 +536,7 @@
         assertEquals(initialServiceInfo.getSubtypes(), Collections.singletonList("ABCDE"));
         assertNull(initialServiceInfo.getAttributeByKey("key"));
         assertEquals(initialServiceInfo.getInterfaceIndex(), 20);
-        assertEquals(NETWORK, initialServiceInfo.getNetwork());
+        assertEquals(mockNetwork, initialServiceInfo.getNetwork());
 
         // Verify onServiceUpdated was called once for the second response.
         verify(mockListenerOne).onServiceUpdated(serviceInfoCaptor.capture());
@@ -492,7 +548,7 @@
         assertEquals(updatedServiceInfo.getSubtypes(), Collections.singletonList("ABCDE"));
         assertEquals(updatedServiceInfo.getAttributeByKey("key"), "value");
         assertEquals(updatedServiceInfo.getInterfaceIndex(), 20);
-        assertEquals(NETWORK, updatedServiceInfo.getNetwork());
+        assertEquals(mockNetwork, updatedServiceInfo.getNetwork());
     }
 
     @Test
@@ -501,44 +557,30 @@
         client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
 
         // Process the initial response.
-        MdnsResponse initialResponse =
-                createResponse(
-                        "service-instance-1",
-                        ipV6Address,
-                        5353,
-                        /* subtype= */ "ABCDE",
-                        Collections.emptyMap(),
-                        /* interfaceIndex= */ 20,
-                        NETWORK);
-        client.processResponse(initialResponse);
+        client.processResponse(createResponse(
+                "service-instance-1", ipV6Address, 5353,
+                /* subtype= */ "ABCDE",
+                Collections.emptyMap(), TEST_TTL), /* interfaceIndex= */ 20, mockNetwork);
 
         // Process a second response with a different port and updated text attributes.
-        MdnsResponse secondResponse =
-                createResponse(
-                        "service-instance-1",
-                        ipV6Address,
-                        5354,
-                        /* subtype= */ "ABCDE",
-                        Collections.singletonMap("key", "value"),
-                        /* interfaceIndex= */ 20,
-                        NETWORK);
-        client.processResponse(secondResponse);
-
-        System.out.println("secondResponses ip"
-                + secondResponse.getInet6AddressRecord().getInet6Address().getHostAddress());
+        client.processResponse(createResponse(
+                "service-instance-1", ipV6Address, 5354,
+                /* subtype= */ "ABCDE",
+                Collections.singletonMap("key", "value"), TEST_TTL),
+                /* interfaceIndex= */ 20, mockNetwork);
 
         // Verify onServiceNameDiscovered was called once for the initial response.
         verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
                 "service-instance-1",
                 SERVICE_TYPE_LABELS,
-                null /* ipv4Address */,
-                ipV6Address /* ipv6Address */,
+                List.of() /* ipv4Address */,
+                List.of(ipV6Address) /* ipv6Address */,
                 5353 /* port */,
                 Collections.singletonList("ABCDE") /* subTypes */,
                 Collections.singletonMap("key", null) /* attributes */,
                 20 /* interfaceIndex */,
-                NETWORK);
+                mockNetwork);
 
         // Verify onServiceFound was called once for the initial response.
         verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
@@ -549,7 +591,7 @@
         assertEquals(initialServiceInfo.getSubtypes(), Collections.singletonList("ABCDE"));
         assertNull(initialServiceInfo.getAttributeByKey("key"));
         assertEquals(initialServiceInfo.getInterfaceIndex(), 20);
-        assertEquals(NETWORK, initialServiceInfo.getNetwork());
+        assertEquals(mockNetwork, initialServiceInfo.getNetwork());
 
         // Verify onServiceUpdated was called once for the second response.
         verify(mockListenerOne).onServiceUpdated(serviceInfoCaptor.capture());
@@ -561,7 +603,7 @@
         assertEquals(updatedServiceInfo.getSubtypes(), Collections.singletonList("ABCDE"));
         assertEquals(updatedServiceInfo.getAttributeByKey("key"), "value");
         assertEquals(updatedServiceInfo.getInterfaceIndex(), 20);
-        assertEquals(NETWORK, updatedServiceInfo.getNetwork());
+        assertEquals(mockNetwork, updatedServiceInfo.getNetwork());
     }
 
     private void verifyServiceRemovedNoCallback(MdnsServiceBrowserListener listener) {
@@ -591,48 +633,38 @@
         final String serviceName = "service-instance-1";
         final String ipV6Address = "2000:3333::da6c:63ff:fe7c:7483";
         // Process the initial response.
-        final MdnsResponse initialResponse =
-                createResponse(
-                        serviceName,
-                        ipV6Address,
-                        5353 /* port */,
-                        /* subtype= */ "ABCDE",
-                        Collections.emptyMap(),
-                        INTERFACE_INDEX,
-                        NETWORK);
-        client.processResponse(initialResponse);
-        MdnsResponse response = mock(MdnsResponse.class);
-        doReturn("goodbye-service").when(response).getServiceInstanceName();
-        doReturn(INTERFACE_INDEX).when(response).getInterfaceIndex();
-        doReturn(NETWORK).when(response).getNetwork();
-        doReturn(true).when(response).isGoodbye();
-        client.processResponse(response);
+        client.processResponse(createResponse(
+                serviceName, ipV6Address, 5353,
+                SERVICE_TYPE_LABELS,
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
+
+        client.processResponse(createResponse(
+                "goodbye-service", ipV6Address, 5353,
+                SERVICE_TYPE_LABELS,
+                Collections.emptyMap(), /* ptrTtlMillis= */ 0L), INTERFACE_INDEX, mockNetwork);
+
         // Verify removed callback won't be called if the service is not existed.
         verifyServiceRemovedNoCallback(mockListenerOne);
         verifyServiceRemovedNoCallback(mockListenerTwo);
 
         // Verify removed callback would be called.
-        doReturn(serviceName).when(response).getServiceInstanceName();
-        client.processResponse(response);
+        client.processResponse(createResponse(
+                serviceName, ipV6Address, 5353,
+                SERVICE_TYPE_LABELS,
+                Collections.emptyMap(), 0L), INTERFACE_INDEX, mockNetwork);
         verifyServiceRemovedCallback(
-                mockListenerOne, serviceName, SERVICE_TYPE_LABELS, INTERFACE_INDEX, NETWORK);
+                mockListenerOne, serviceName, SERVICE_TYPE_LABELS, INTERFACE_INDEX, mockNetwork);
         verifyServiceRemovedCallback(
-                mockListenerTwo, serviceName, SERVICE_TYPE_LABELS, INTERFACE_INDEX, NETWORK);
+                mockListenerTwo, serviceName, SERVICE_TYPE_LABELS, INTERFACE_INDEX, mockNetwork);
     }
 
     @Test
     public void reportExistingServiceToNewlyRegisteredListeners() throws Exception {
         // Process the initial response.
-        MdnsResponse initialResponse =
-                createResponse(
-                        "service-instance-1",
-                        "192.168.1.1",
-                        5353,
-                        /* subtype= */ "ABCDE",
-                        Collections.emptyMap(),
-                        INTERFACE_INDEX,
-                        NETWORK);
-        client.processResponse(initialResponse);
+        client.processResponse(createResponse(
+                "service-instance-1", "192.168.1.1", 5353,
+                /* subtype= */ "ABCDE",
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
 
         client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
 
@@ -641,13 +673,13 @@
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
                 "service-instance-1",
                 SERVICE_TYPE_LABELS,
-                "192.168.1.1" /* ipv4Address */,
-                null /* ipv6Address */,
+                List.of("192.168.1.1") /* ipv4Address */,
+                List.of() /* ipv6Address */,
                 5353 /* port */,
                 Collections.singletonList("ABCDE") /* subTypes */,
                 Collections.singletonMap("key", null) /* attributes */,
                 INTERFACE_INDEX,
-                NETWORK);
+                mockNetwork);
 
         // Verify onServiceFound was called once for the existing response.
         verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
@@ -659,10 +691,10 @@
         assertNull(existingServiceInfo.getAttributeByKey("key"));
 
         // Process a goodbye message for the existing response.
-        MdnsResponse goodByeResponse = mock(MdnsResponse.class);
-        when(goodByeResponse.getServiceInstanceName()).thenReturn("service-instance-1");
-        when(goodByeResponse.isGoodbye()).thenReturn(true);
-        client.processResponse(goodByeResponse);
+        client.processResponse(createResponse(
+                "service-instance-1", "192.168.1.1", 5353,
+                SERVICE_TYPE_LABELS,
+                Collections.emptyMap(), /* ptrTtlMillis= */ 0L), INTERFACE_INDEX, mockNetwork);
 
         client.startSendAndReceive(mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
 
@@ -681,17 +713,15 @@
         Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
 
         // Process the initial response.
-        MdnsResponse initialResponse =
-                createMockResponse(
-                        serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
-                        Map.of(), INTERFACE_INDEX, NETWORK);
-        client.processResponse(initialResponse);
+        client.processResponse(createResponse(
+                serviceInstanceName, "192.168.1.1", 5353, /* subtype= */ "ABCDE",
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
 
         // Clear the scheduled runnable.
         currentThreadExecutor.getAndClearLastScheduledRunnable();
 
         // Simulate the case where the response is after TTL.
-        when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
+        doReturn(TEST_ELAPSED_REALTIME + TEST_TTL + 1L).when(mockDecoderClock).elapsedRealtime();
         firstMdnsTask.run();
 
         // Verify removed callback was not called.
@@ -705,7 +735,8 @@
         //MdnsConfigsFlagsImpl.allowSearchOptionsToRemoveExpiredService.override(true);
         final String serviceInstanceName = "service-instance-1";
         client =
-                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor) {
+                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
+                        mockDecoderClock, mockNetwork, mockSharedLog) {
                     @Override
                     MdnsPacketWriter createMdnsPacketWriter() {
                         return mockPacketWriter;
@@ -715,29 +746,27 @@
         Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
 
         // Process the initial response.
-        MdnsResponse initialResponse =
-                createMockResponse(
-                        serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
-                        Map.of(), INTERFACE_INDEX, NETWORK);
-        client.processResponse(initialResponse);
+        client.processResponse(createResponse(
+                serviceInstanceName, "192.168.1.1", 5353, /* subtype= */ "ABCDE",
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
 
         // Clear the scheduled runnable.
         currentThreadExecutor.getAndClearLastScheduledRunnable();
 
         // Simulate the case where the response is under TTL.
-        when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 1000);
+        doReturn(TEST_ELAPSED_REALTIME + TEST_TTL - 1L).when(mockDecoderClock).elapsedRealtime();
         firstMdnsTask.run();
 
         // Verify removed callback was not called.
         verifyServiceRemovedNoCallback(mockListenerOne);
 
         // Simulate the case where the response is after TTL.
-        when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
+        doReturn(TEST_ELAPSED_REALTIME + TEST_TTL + 1L).when(mockDecoderClock).elapsedRealtime();
         firstMdnsTask.run();
 
         // Verify removed callback was called.
         verifyServiceRemovedCallback(mockListenerOne, serviceInstanceName, SERVICE_TYPE_LABELS,
-                INTERFACE_INDEX, NETWORK);
+                INTERFACE_INDEX, mockNetwork);
     }
 
     @Test
@@ -745,7 +774,8 @@
             throws Exception {
         final String serviceInstanceName = "service-instance-1";
         client =
-                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor) {
+                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
+                        mockDecoderClock, mockNetwork, mockSharedLog) {
                     @Override
                     MdnsPacketWriter createMdnsPacketWriter() {
                         return mockPacketWriter;
@@ -755,17 +785,15 @@
         Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
 
         // Process the initial response.
-        MdnsResponse initialResponse =
-                createMockResponse(
-                        serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
-                        Map.of(), INTERFACE_INDEX, NETWORK);
-        client.processResponse(initialResponse);
+        client.processResponse(createResponse(
+                serviceInstanceName, "192.168.1.1", 5353, /* subtype= */ "ABCDE",
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
 
         // Clear the scheduled runnable.
         currentThreadExecutor.getAndClearLastScheduledRunnable();
 
         // Simulate the case where the response is after TTL.
-        when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
+        doReturn(TEST_ELAPSED_REALTIME + TEST_TTL + 1L).when(mockDecoderClock).elapsedRealtime();
         firstMdnsTask.run();
 
         // Verify removed callback was not called.
@@ -779,7 +807,8 @@
         //MdnsConfigsFlagsImpl.removeServiceAfterTtlExpires.override(true);
         final String serviceInstanceName = "service-instance-1";
         client =
-                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor) {
+                new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
+                        mockDecoderClock, mockNetwork, mockSharedLog) {
                     @Override
                     MdnsPacketWriter createMdnsPacketWriter() {
                         return mockPacketWriter;
@@ -789,22 +818,20 @@
         Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
 
         // Process the initial response.
-        MdnsResponse initialResponse =
-                createMockResponse(
-                        serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
-                        Map.of(), INTERFACE_INDEX, NETWORK);
-        client.processResponse(initialResponse);
+        client.processResponse(createResponse(
+                serviceInstanceName, "192.168.1.1", 5353, /* subtype= */ "ABCDE",
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
 
         // Clear the scheduled runnable.
         currentThreadExecutor.getAndClearLastScheduledRunnable();
 
         // Simulate the case where the response is after TTL.
-        when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
+        doReturn(TEST_ELAPSED_REALTIME + TEST_TTL + 1L).when(mockDecoderClock).elapsedRealtime();
         firstMdnsTask.run();
 
         // Verify removed callback was called.
         verifyServiceRemovedCallback(mockListenerOne, serviceInstanceName, SERVICE_TYPE_LABELS,
-                INTERFACE_INDEX, NETWORK);
+                INTERFACE_INDEX, mockNetwork);
     }
 
     @Test
@@ -816,122 +843,430 @@
         InOrder inOrder = inOrder(mockListenerOne);
 
         // Process the initial response which is incomplete.
-        final MdnsResponse initialResponse =
-                createResponse(
-                        serviceName,
-                        null,
-                        5353,
-                        "ABCDE" /* subtype */,
-                        Collections.emptyMap(),
-                        INTERFACE_INDEX,
-                        NETWORK);
-        client.processResponse(initialResponse);
+        final String subtype = "ABCDE";
+        client.processResponse(createResponse(
+                serviceName, null, 5353, subtype,
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
 
         // Process a second response which has ip address to make response become complete.
-        final MdnsResponse secondResponse =
-                createResponse(
-                        serviceName,
-                        ipV4Address,
-                        5353,
-                        "ABCDE" /* subtype */,
-                        Collections.emptyMap(),
-                        INTERFACE_INDEX,
-                        NETWORK);
-        client.processResponse(secondResponse);
+        client.processResponse(createResponse(
+                serviceName, ipV4Address, 5353, subtype,
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
 
         // Process a third response with a different ip address, port and updated text attributes.
-        final MdnsResponse thirdResponse =
-                createResponse(
-                        serviceName,
-                        ipV6Address,
-                        5354,
-                        "ABCDE" /* subtype */,
-                        Collections.singletonMap("key", "value"),
-                        INTERFACE_INDEX,
-                        NETWORK);
-        client.processResponse(thirdResponse);
+        client.processResponse(createResponse(
+                serviceName, ipV6Address, 5354, subtype,
+                Collections.singletonMap("key", "value"), TEST_TTL), INTERFACE_INDEX, mockNetwork);
 
-        // Process the last response which is goodbye message.
-        final MdnsResponse lastResponse = mock(MdnsResponse.class);
-        doReturn(serviceName).when(lastResponse).getServiceInstanceName();
-        doReturn(true).when(lastResponse).isGoodbye();
-        client.processResponse(lastResponse);
+        // Process the last response which is goodbye message (with the main type, not subtype).
+        client.processResponse(createResponse(
+                serviceName, ipV6Address, 5354, SERVICE_TYPE_LABELS,
+                Collections.singletonMap("key", "value"), /* ptrTtlMillis= */ 0L),
+                INTERFACE_INDEX, mockNetwork);
 
         // Verify onServiceNameDiscovered was first called for the initial response.
         inOrder.verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
                 serviceName,
                 SERVICE_TYPE_LABELS,
-                null /* ipv4Address */,
-                null /* ipv6Address */,
+                List.of() /* ipv4Address */,
+                List.of() /* ipv6Address */,
                 5353 /* port */,
-                Collections.singletonList("ABCDE") /* subTypes */,
+                Collections.singletonList(subtype) /* subTypes */,
                 Collections.singletonMap("key", null) /* attributes */,
                 INTERFACE_INDEX,
-                NETWORK);
+                mockNetwork);
 
         // Verify onServiceFound was second called for the second response.
         inOrder.verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(1),
                 serviceName,
                 SERVICE_TYPE_LABELS,
-                ipV4Address /* ipv4Address */,
-                null /* ipv6Address */,
+                List.of(ipV4Address) /* ipv4Address */,
+                List.of() /* ipv6Address */,
                 5353 /* port */,
-                Collections.singletonList("ABCDE") /* subTypes */,
+                Collections.singletonList(subtype) /* subTypes */,
                 Collections.singletonMap("key", null) /* attributes */,
                 INTERFACE_INDEX,
-                NETWORK);
+                mockNetwork);
 
         // Verify onServiceUpdated was third called for the third response.
         inOrder.verify(mockListenerOne).onServiceUpdated(serviceInfoCaptor.capture());
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(2),
                 serviceName,
                 SERVICE_TYPE_LABELS,
-                ipV4Address /* ipv4Address */,
-                ipV6Address /* ipv6Address */,
+                List.of(ipV4Address) /* ipv4Address */,
+                List.of(ipV6Address) /* ipv6Address */,
                 5354 /* port */,
-                Collections.singletonList("ABCDE") /* subTypes */,
+                Collections.singletonList(subtype) /* subTypes */,
                 Collections.singletonMap("key", "value") /* attributes */,
                 INTERFACE_INDEX,
-                NETWORK);
+                mockNetwork);
 
         // Verify onServiceRemoved was called for the last response.
         inOrder.verify(mockListenerOne).onServiceRemoved(serviceInfoCaptor.capture());
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(3),
                 serviceName,
                 SERVICE_TYPE_LABELS,
-                ipV4Address /* ipv4Address */,
-                ipV6Address /* ipv6Address */,
+                List.of(ipV4Address) /* ipv4Address */,
+                List.of(ipV6Address) /* ipv6Address */,
                 5354 /* port */,
                 Collections.singletonList("ABCDE") /* subTypes */,
                 Collections.singletonMap("key", "value") /* attributes */,
                 INTERFACE_INDEX,
-                NETWORK);
+                mockNetwork);
 
         // Verify onServiceNameRemoved was called for the last response.
         inOrder.verify(mockListenerOne).onServiceNameRemoved(serviceInfoCaptor.capture());
         verifyServiceInfo(serviceInfoCaptor.getAllValues().get(4),
                 serviceName,
                 SERVICE_TYPE_LABELS,
-                ipV4Address /* ipv4Address */,
-                ipV6Address /* ipv6Address */,
+                List.of(ipV4Address) /* ipv4Address */,
+                List.of(ipV6Address) /* ipv6Address */,
                 5354 /* port */,
                 Collections.singletonList("ABCDE") /* subTypes */,
                 Collections.singletonMap("key", "value") /* attributes */,
                 INTERFACE_INDEX,
-                NETWORK);
+                mockNetwork);
+    }
+
+    @Test
+    public void testProcessResponse_Resolve() throws Exception {
+        client = new MdnsServiceTypeClient(
+                SERVICE_TYPE, mockSocketClient, currentThreadExecutor, mockNetwork, mockSharedLog);
+
+        final String instanceName = "service-instance";
+        final String[] hostname = new String[] { "testhost "};
+        final String ipV4Address = "192.0.2.0";
+        final String ipV6Address = "2001:db8::";
+
+        final MdnsSearchOptions resolveOptions = MdnsSearchOptions.newBuilder()
+                .setResolveInstanceName(instanceName).build();
+
+        client.startSendAndReceive(mockListenerOne, resolveOptions);
+        InOrder inOrder = inOrder(mockListenerOne, mockSocketClient);
+
+        // Verify a query for SRV/TXT was sent, but no PTR query
+        final ArgumentCaptor<DatagramPacket> srvTxtQueryCaptor =
+                ArgumentCaptor.forClass(DatagramPacket.class);
+        currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+        // Send twice for IPv4 and IPv6
+        inOrder.verify(mockSocketClient, times(2)).sendUnicastPacket(srvTxtQueryCaptor.capture(),
+                eq(mockNetwork));
+
+        final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
+                new MdnsPacketReader(srvTxtQueryCaptor.getValue()));
+        final List<MdnsRecord> srvTxtQuestions = srvTxtQueryPacket.questions;
+
+        final String[] serviceName = Stream.concat(Stream.of(instanceName),
+                Arrays.stream(SERVICE_TYPE_LABELS)).toArray(String[]::new);
+        assertFalse(srvTxtQuestions.stream().anyMatch(q -> q.getType() == MdnsRecord.TYPE_PTR));
+        assertTrue(srvTxtQuestions.stream().anyMatch(q ->
+                q.getType() == MdnsRecord.TYPE_SRV && Arrays.equals(q.name, serviceName)));
+        assertTrue(srvTxtQuestions.stream().anyMatch(q ->
+                q.getType() == MdnsRecord.TYPE_TXT && Arrays.equals(q.name, serviceName)));
+
+        // Process a response with SRV+TXT
+        final MdnsPacket srvTxtResponse = new MdnsPacket(
+                0 /* flags */,
+                Collections.emptyList() /* questions */,
+                List.of(
+                        new MdnsServiceRecord(serviceName, 0L /* receiptTimeMillis */,
+                                true /* cacheFlush */, TEST_TTL, 0 /* servicePriority */,
+                                0 /* serviceWeight */, 1234 /* servicePort */, hostname),
+                        new MdnsTextRecord(serviceName, 0L /* receiptTimeMillis */,
+                                true /* cacheFlush */, TEST_TTL,
+                                Collections.emptyList() /* entries */)),
+                Collections.emptyList() /* authorityRecords */,
+                Collections.emptyList() /* additionalRecords */);
+
+        client.processResponse(srvTxtResponse, INTERFACE_INDEX, mockNetwork);
+
+        // Expect a query for A/AAAA
+        final ArgumentCaptor<DatagramPacket> addressQueryCaptor =
+                ArgumentCaptor.forClass(DatagramPacket.class);
+        currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+        inOrder.verify(mockSocketClient, times(2)).sendMulticastPacket(addressQueryCaptor.capture(),
+                eq(mockNetwork));
+
+        final MdnsPacket addressQueryPacket = MdnsPacket.parse(
+                new MdnsPacketReader(addressQueryCaptor.getValue()));
+        final List<MdnsRecord> addressQueryQuestions = addressQueryPacket.questions;
+        assertTrue(addressQueryQuestions.stream().anyMatch(q ->
+                q.getType() == MdnsRecord.TYPE_A && Arrays.equals(q.name, hostname)));
+        assertTrue(addressQueryQuestions.stream().anyMatch(q ->
+                q.getType() == MdnsRecord.TYPE_AAAA && Arrays.equals(q.name, hostname)));
+
+        // Process a response with address records
+        final MdnsPacket addressResponse = new MdnsPacket(
+                0 /* flags */,
+                Collections.emptyList() /* questions */,
+                List.of(
+                        new MdnsInetAddressRecord(hostname, 0L /* receiptTimeMillis */,
+                                true /* cacheFlush */, TEST_TTL,
+                                InetAddresses.parseNumericAddress(ipV4Address)),
+                        new MdnsInetAddressRecord(hostname, 0L /* receiptTimeMillis */,
+                                true /* cacheFlush */, TEST_TTL,
+                                InetAddresses.parseNumericAddress(ipV6Address))),
+                Collections.emptyList() /* authorityRecords */,
+                Collections.emptyList() /* additionalRecords */);
+
+        inOrder.verify(mockListenerOne, never()).onServiceNameDiscovered(any());
+        client.processResponse(addressResponse, INTERFACE_INDEX, mockNetwork);
+
+        inOrder.verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
+        verifyServiceInfo(serviceInfoCaptor.getValue(),
+                instanceName,
+                SERVICE_TYPE_LABELS,
+                List.of(ipV4Address),
+                List.of(ipV6Address),
+                1234 /* port */,
+                Collections.emptyList() /* subTypes */,
+                Collections.emptyMap() /* attributes */,
+                INTERFACE_INDEX,
+                mockNetwork);
+    }
+
+    @Test
+    public void testProcessResponse_ResolveExcludesOtherServices() {
+        client = new MdnsServiceTypeClient(
+                SERVICE_TYPE, mockSocketClient, currentThreadExecutor, mockNetwork, mockSharedLog);
+
+        final String requestedInstance = "instance1";
+        final String otherInstance = "instance2";
+        final String ipV4Address = "192.0.2.0";
+        final String ipV6Address = "2001:db8::";
+        final String capitalizedRequestInstance = "Instance1";
+
+        final MdnsSearchOptions resolveOptions = MdnsSearchOptions.newBuilder()
+                // Use different case in the options
+                .setResolveInstanceName(capitalizedRequestInstance).build();
+
+        client.startSendAndReceive(mockListenerOne, resolveOptions);
+        client.startSendAndReceive(mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
+
+        // Complete response from instanceName
+        client.processResponse(createResponse(
+                requestedInstance, ipV4Address, 5353, SERVICE_TYPE_LABELS,
+                        Collections.emptyMap() /* textAttributes */, TEST_TTL),
+                INTERFACE_INDEX, mockNetwork);
+
+        // Complete response from otherInstanceName
+        client.processResponse(createResponse(
+                otherInstance, ipV4Address, 5353, SERVICE_TYPE_LABELS,
+                        Collections.emptyMap() /* textAttributes */, TEST_TTL),
+                INTERFACE_INDEX, mockNetwork);
+
+        // Address update from otherInstanceName
+        client.processResponse(createResponse(
+                otherInstance, ipV6Address, 5353, SERVICE_TYPE_LABELS,
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
+
+        // Goodbye from otherInstanceName
+        client.processResponse(createResponse(
+                otherInstance, ipV6Address, 5353, SERVICE_TYPE_LABELS,
+                Collections.emptyMap(), 0L /* ttl */), INTERFACE_INDEX, mockNetwork);
+
+        // mockListenerOne gets notified for the requested instance
+        verify(mockListenerOne).onServiceNameDiscovered(
+                matchServiceName(capitalizedRequestInstance));
+        verify(mockListenerOne).onServiceFound(matchServiceName(capitalizedRequestInstance));
+
+        // ...but does not get any callback for the other instance
+        verify(mockListenerOne, never()).onServiceFound(matchServiceName(otherInstance));
+        verify(mockListenerOne, never()).onServiceNameDiscovered(matchServiceName(otherInstance));
+        verify(mockListenerOne, never()).onServiceUpdated(matchServiceName(otherInstance));
+        verify(mockListenerOne, never()).onServiceRemoved(matchServiceName(otherInstance));
+
+        // mockListenerTwo gets notified for both though
+        final InOrder inOrder = inOrder(mockListenerTwo);
+        inOrder.verify(mockListenerTwo).onServiceNameDiscovered(
+                matchServiceName(capitalizedRequestInstance));
+        inOrder.verify(mockListenerTwo).onServiceFound(
+                matchServiceName(capitalizedRequestInstance));
+
+        inOrder.verify(mockListenerTwo).onServiceNameDiscovered(matchServiceName(otherInstance));
+        inOrder.verify(mockListenerTwo).onServiceFound(matchServiceName(otherInstance));
+        inOrder.verify(mockListenerTwo).onServiceUpdated(matchServiceName(otherInstance));
+        inOrder.verify(mockListenerTwo).onServiceRemoved(matchServiceName(otherInstance));
+    }
+
+    @Test
+    public void testProcessResponse_SubtypeDiscoveryLimitedToSubtype() {
+        client = new MdnsServiceTypeClient(
+                SERVICE_TYPE, mockSocketClient, currentThreadExecutor, mockNetwork, mockSharedLog);
+
+        final String matchingInstance = "instance1";
+        final String subtype = "_subtype";
+        final String otherInstance = "instance2";
+        final String ipV4Address = "192.0.2.0";
+        final String ipV6Address = "2001:db8::";
+
+        final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
+                // Search with different case. Note MdnsSearchOptions subtype doesn't start with "_"
+                .addSubtype("Subtype").build();
+
+        client.startSendAndReceive(mockListenerOne, options);
+        client.startSendAndReceive(mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
+
+        // Complete response from instanceName
+        final MdnsPacket packetWithoutSubtype = createResponse(
+                matchingInstance, ipV4Address, 5353, SERVICE_TYPE_LABELS,
+                Collections.emptyMap() /* textAttributes */, TEST_TTL);
+        final MdnsPointerRecord originalPtr = (MdnsPointerRecord) CollectionUtils.findFirst(
+                packetWithoutSubtype.answers, r -> r instanceof MdnsPointerRecord);
+
+        // Add a subtype PTR record
+        final ArrayList<MdnsRecord> newAnswers = new ArrayList<>(packetWithoutSubtype.answers);
+        newAnswers.add(new MdnsPointerRecord(
+                // PTR should be _subtype._sub._type._tcp.local -> instance1._type._tcp.local
+                Stream.concat(Stream.of(subtype, "_sub"), Arrays.stream(SERVICE_TYPE_LABELS))
+                        .toArray(String[]::new),
+                originalPtr.getReceiptTime(), originalPtr.getCacheFlush(), originalPtr.getTtl(),
+                originalPtr.getPointer()));
+        final MdnsPacket packetWithSubtype = new MdnsPacket(
+                packetWithoutSubtype.flags,
+                packetWithoutSubtype.questions,
+                newAnswers,
+                packetWithoutSubtype.authorityRecords,
+                packetWithoutSubtype.additionalRecords);
+        client.processResponse(packetWithSubtype, INTERFACE_INDEX, mockNetwork);
+
+        // Complete response from otherInstanceName, without subtype
+        client.processResponse(createResponse(
+                        otherInstance, ipV4Address, 5353, SERVICE_TYPE_LABELS,
+                        Collections.emptyMap() /* textAttributes */, TEST_TTL),
+                INTERFACE_INDEX, mockNetwork);
+
+        // Address update from otherInstanceName
+        client.processResponse(createResponse(
+                otherInstance, ipV6Address, 5353, SERVICE_TYPE_LABELS,
+                Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
+
+        // Goodbye from otherInstanceName
+        client.processResponse(createResponse(
+                otherInstance, ipV6Address, 5353, SERVICE_TYPE_LABELS,
+                Collections.emptyMap(), 0L /* ttl */), INTERFACE_INDEX, mockNetwork);
+
+        // mockListenerOne gets notified for the requested instance
+        final ArgumentMatcher<MdnsServiceInfo> subtypeInstanceMatcher = info ->
+                info.getServiceInstanceName().equals(matchingInstance)
+                        && info.getSubtypes().equals(Collections.singletonList(subtype));
+        verify(mockListenerOne).onServiceNameDiscovered(argThat(subtypeInstanceMatcher));
+        verify(mockListenerOne).onServiceFound(argThat(subtypeInstanceMatcher));
+
+        // ...but does not get any callback for the other instance
+        verify(mockListenerOne, never()).onServiceFound(matchServiceName(otherInstance));
+        verify(mockListenerOne, never()).onServiceNameDiscovered(matchServiceName(otherInstance));
+        verify(mockListenerOne, never()).onServiceUpdated(matchServiceName(otherInstance));
+        verify(mockListenerOne, never()).onServiceRemoved(matchServiceName(otherInstance));
+
+        // mockListenerTwo gets notified for both though
+        final InOrder inOrder = inOrder(mockListenerTwo);
+        inOrder.verify(mockListenerTwo).onServiceNameDiscovered(argThat(subtypeInstanceMatcher));
+        inOrder.verify(mockListenerTwo).onServiceFound(argThat(subtypeInstanceMatcher));
+
+        inOrder.verify(mockListenerTwo).onServiceNameDiscovered(matchServiceName(otherInstance));
+        inOrder.verify(mockListenerTwo).onServiceFound(matchServiceName(otherInstance));
+        inOrder.verify(mockListenerTwo).onServiceUpdated(matchServiceName(otherInstance));
+        inOrder.verify(mockListenerTwo).onServiceRemoved(matchServiceName(otherInstance));
+    }
+
+    @Test
+    public void testNotifySocketDestroyed() throws Exception {
+        client = new MdnsServiceTypeClient(
+                SERVICE_TYPE, mockSocketClient, currentThreadExecutor, mockNetwork, mockSharedLog);
+
+        final String requestedInstance = "instance1";
+        final String otherInstance = "instance2";
+        final String ipV4Address = "192.0.2.0";
+
+        final MdnsSearchOptions resolveOptions = MdnsSearchOptions.newBuilder()
+                .setResolveInstanceName("instance1").build();
+
+        client.startSendAndReceive(mockListenerOne, resolveOptions);
+        // Ensure the first task is executed so it schedules a future task
+        currentThreadExecutor.getAndClearSubmittedFuture().get(
+                TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        client.startSendAndReceive(mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
+
+        // Filing the second request cancels the first future
+        verify(expectedSendFutures[0]).cancel(true);
+
+        // Ensure it gets executed too
+        currentThreadExecutor.getAndClearSubmittedFuture().get(
+                TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+        // Complete response from instanceName
+        client.processResponse(createResponse(
+                        requestedInstance, ipV4Address, 5353, SERVICE_TYPE_LABELS,
+                        Collections.emptyMap() /* textAttributes */, TEST_TTL),
+                INTERFACE_INDEX, mockNetwork);
+
+        // Complete response from otherInstanceName
+        client.processResponse(createResponse(
+                        otherInstance, ipV4Address, 5353, SERVICE_TYPE_LABELS,
+                        Collections.emptyMap() /* textAttributes */, TEST_TTL),
+                INTERFACE_INDEX, mockNetwork);
+
+        verify(expectedSendFutures[1], never()).cancel(true);
+        client.notifySocketDestroyed();
+        verify(expectedSendFutures[1]).cancel(true);
+
+        // mockListenerOne gets notified for the requested instance
+        final InOrder inOrder1 = inOrder(mockListenerOne);
+        inOrder1.verify(mockListenerOne).onServiceNameDiscovered(
+                matchServiceName(requestedInstance));
+        inOrder1.verify(mockListenerOne).onServiceFound(matchServiceName(requestedInstance));
+        inOrder1.verify(mockListenerOne).onServiceRemoved(matchServiceName(requestedInstance));
+        inOrder1.verify(mockListenerOne).onServiceNameRemoved(matchServiceName(requestedInstance));
+        verify(mockListenerOne, never()).onServiceFound(matchServiceName(otherInstance));
+        verify(mockListenerOne, never()).onServiceNameDiscovered(matchServiceName(otherInstance));
+        verify(mockListenerOne, never()).onServiceRemoved(matchServiceName(otherInstance));
+        verify(mockListenerOne, never()).onServiceNameRemoved(matchServiceName(otherInstance));
+
+        // mockListenerTwo gets notified for both though
+        final InOrder inOrder2 = inOrder(mockListenerTwo);
+        inOrder2.verify(mockListenerTwo).onServiceNameDiscovered(
+                matchServiceName(requestedInstance));
+        inOrder2.verify(mockListenerTwo).onServiceFound(matchServiceName(requestedInstance));
+        inOrder2.verify(mockListenerTwo).onServiceNameDiscovered(matchServiceName(otherInstance));
+        inOrder2.verify(mockListenerTwo).onServiceFound(matchServiceName(otherInstance));
+        inOrder2.verify(mockListenerTwo).onServiceRemoved(matchServiceName(otherInstance));
+        inOrder2.verify(mockListenerTwo).onServiceNameRemoved(matchServiceName(otherInstance));
+        inOrder2.verify(mockListenerTwo).onServiceRemoved(matchServiceName(requestedInstance));
+        inOrder2.verify(mockListenerTwo).onServiceNameRemoved(matchServiceName(requestedInstance));
+    }
+
+    private static MdnsServiceInfo matchServiceName(String name) {
+        return argThat(info -> info.getServiceInstanceName().equals(name));
     }
 
     // verifies that the right query was enqueued with the right delay, and send query by executing
     // the runnable.
     private void verifyAndSendQuery(int index, long timeInMs, boolean expectsUnicastResponse) {
+        verifyAndSendQuery(
+                index, timeInMs, expectsUnicastResponse, true /* multipleSocketDiscovery */);
+    }
+
+    private void verifyAndSendQuery(int index, long timeInMs, boolean expectsUnicastResponse,
+            boolean multipleSocketDiscovery) {
         assertEquals(currentThreadExecutor.getAndClearLastScheduledDelayInMs(), timeInMs);
         currentThreadExecutor.getAndClearLastScheduledRunnable().run();
         if (expectsUnicastResponse) {
-            verify(mockSocketClient).sendUnicastPacket(expectedPackets[index]);
+            verify(mockSocketClient).sendUnicastPacket(
+                    expectedIPv4Packets[index], mockNetwork);
+            if (multipleSocketDiscovery) {
+                verify(mockSocketClient).sendUnicastPacket(
+                        expectedIPv6Packets[index], mockNetwork);
+            }
         } else {
-            verify(mockSocketClient).sendMulticastPacket(expectedPackets[index]);
+            verify(mockSocketClient).sendMulticastPacket(
+                    expectedIPv4Packets[index], mockNetwork);
+            if (multipleSocketDiscovery) {
+                verify(mockSocketClient).sendMulticastPacket(
+                        expectedIPv6Packets[index], mockNetwork);
+            }
         }
     }
 
@@ -941,6 +1276,7 @@
         private long lastScheduledDelayInMs;
         private Runnable lastScheduledRunnable;
         private Runnable lastSubmittedRunnable;
+        private Future<?> lastSubmittedFuture;
         private int futureIndex;
 
         FakeExecutor() {
@@ -952,6 +1288,7 @@
         public Future<?> submit(Runnable command) {
             Future<?> future = super.submit(command);
             lastSubmittedRunnable = command;
+            lastSubmittedFuture = future;
             return future;
         }
 
@@ -983,108 +1320,76 @@
             lastSubmittedRunnable = null;
             return val;
         }
+
+        Future<?> getAndClearSubmittedFuture() {
+            Future<?> val = lastSubmittedFuture;
+            lastSubmittedFuture = null;
+            return val;
+        }
     }
 
-    // Creates a mock mDNS response.
-    private MdnsResponse createMockResponse(
-            @NonNull String serviceInstanceName,
-            @NonNull String host,
-            int port,
-            @NonNull List<String> subtypes,
-            @NonNull Map<String, String> textAttributes,
-            int interfaceIndex,
-            Network network)
-            throws Exception {
-        String[] hostName = new String[]{"hostname"};
-        MdnsServiceRecord serviceRecord = mock(MdnsServiceRecord.class);
-        when(serviceRecord.getServiceHost()).thenReturn(hostName);
-        when(serviceRecord.getServicePort()).thenReturn(port);
-
-        MdnsResponse response = spy(new MdnsResponse(0, interfaceIndex, network));
-
-        MdnsInetAddressRecord inetAddressRecord = mock(MdnsInetAddressRecord.class);
-        if (host.contains(":")) {
-            when(inetAddressRecord.getInet6Address())
-                    .thenReturn((Inet6Address) Inet6Address.getByName(host));
-            response.setInet6AddressRecord(inetAddressRecord);
-        } else {
-            when(inetAddressRecord.getInet4Address())
-                    .thenReturn((Inet4Address) Inet4Address.getByName(host));
-            response.setInet4AddressRecord(inetAddressRecord);
-        }
-
-        MdnsTextRecord textRecord = mock(MdnsTextRecord.class);
-        List<String> textStrings = new ArrayList<>();
-        List<TextEntry> textEntries = new ArrayList<>();
-        for (Map.Entry<String, String> kv : textAttributes.entrySet()) {
-            textStrings.add(kv.getKey() + "=" + kv.getValue());
-            textEntries.add(new TextEntry(kv.getKey(), kv.getValue().getBytes(UTF_8)));
-        }
-        when(textRecord.getStrings()).thenReturn(textStrings);
-        when(textRecord.getEntries()).thenReturn(textEntries);
-
-        response.setServiceRecord(serviceRecord);
-        response.setTextRecord(textRecord);
-
-        doReturn(false).when(response).isGoodbye();
-        doReturn(true).when(response).isComplete();
-        doReturn(serviceInstanceName).when(response).getServiceInstanceName();
-        doReturn(new ArrayList<>(subtypes)).when(response).getSubtypes();
-        return response;
-    }
-
-    // Creates a mDNS response.
-    private MdnsResponse createResponse(
+    private MdnsPacket createResponse(
             @NonNull String serviceInstanceName,
             @Nullable String host,
             int port,
             @NonNull String subtype,
             @NonNull Map<String, String> textAttributes,
-            int interfaceIndex,
-            Network network)
+            long ptrTtlMillis)
             throws Exception {
-        MdnsResponse response = new MdnsResponse(0, interfaceIndex, network);
+        final ArrayList<String> type = new ArrayList<>();
+        type.add(subtype);
+        type.add(MdnsConstants.SUBTYPE_LABEL);
+        type.addAll(Arrays.asList(SERVICE_TYPE_LABELS));
+        return createResponse(serviceInstanceName, host, port, type.toArray(new String[0]),
+                textAttributes, ptrTtlMillis);
+    }
+
+    // Creates a mDNS response.
+    private MdnsPacket createResponse(
+            @NonNull String serviceInstanceName,
+            @Nullable String host,
+            int port,
+            @NonNull String[] type,
+            @NonNull Map<String, String> textAttributes,
+            long ptrTtlMillis) {
+
+        final ArrayList<MdnsRecord> answerRecords = new ArrayList<>();
 
         // Set PTR record
+        final ArrayList<String> serviceNameList = new ArrayList<>();
+        serviceNameList.add(serviceInstanceName);
+        serviceNameList.addAll(Arrays.asList(type));
+        final String[] serviceName = serviceNameList.toArray(new String[0]);
         final MdnsPointerRecord pointerRecord = new MdnsPointerRecord(
-                new String[]{subtype, MdnsConstants.SUBTYPE_LABEL, "test"} /* name */,
-                0L /* receiptTimeMillis */,
+                type,
+                TEST_ELAPSED_REALTIME /* receiptTimeMillis */,
                 false /* cacheFlush */,
-                120000L /* ttlMillis */,
-                new String[]{serviceInstanceName});
-        response.addPointerRecord(pointerRecord);
+                ptrTtlMillis,
+                serviceName);
+        answerRecords.add(pointerRecord);
 
         // Set SRV record.
         final MdnsServiceRecord serviceRecord = new MdnsServiceRecord(
-                new String[] {"service"} /* name */,
-                0L /* receiptTimeMillis */,
+                serviceName,
+                TEST_ELAPSED_REALTIME /* receiptTimeMillis */,
                 false /* cacheFlush */,
-                120000L /* ttlMillis */,
+                TEST_TTL,
                 0 /* servicePriority */,
                 0 /* serviceWeight */,
                 port,
                 new String[]{"hostname"});
-        response.setServiceRecord(serviceRecord);
+        answerRecords.add(serviceRecord);
 
         // Set A/AAAA record.
         if (host != null) {
-            if (InetAddresses.parseNumericAddress(host) instanceof Inet6Address) {
-                final MdnsInetAddressRecord inetAddressRecord = new MdnsInetAddressRecord(
-                        new String[] {"address"} /* name */,
-                        0L /* receiptTimeMillis */,
-                        false /* cacheFlush */,
-                        120000L /* ttlMillis */,
-                        Inet6Address.getByName(host));
-                response.setInet6AddressRecord(inetAddressRecord);
-            } else {
-                final MdnsInetAddressRecord inetAddressRecord = new MdnsInetAddressRecord(
-                        new String[] {"address"} /* name */,
-                        0L /* receiptTimeMillis */,
-                        false /* cacheFlush */,
-                        120000L /* ttlMillis */,
-                        Inet4Address.getByName(host));
-                response.setInet4AddressRecord(inetAddressRecord);
-            }
+            final InetAddress addr = InetAddresses.parseNumericAddress(host);
+            final MdnsInetAddressRecord inetAddressRecord = new MdnsInetAddressRecord(
+                    new String[] {"hostname"} /* name */,
+                    TEST_ELAPSED_REALTIME /* receiptTimeMillis */,
+                    false /* cacheFlush */,
+                    TEST_TTL,
+                    addr);
+            answerRecords.add(inetAddressRecord);
         }
 
         // Set TXT record.
@@ -1093,12 +1398,18 @@
             textEntries.add(new TextEntry(kv.getKey(), kv.getValue().getBytes(UTF_8)));
         }
         final MdnsTextRecord textRecord = new MdnsTextRecord(
-                new String[] {"text"} /* name */,
-                0L /* receiptTimeMillis */,
+                serviceName,
+                TEST_ELAPSED_REALTIME /* receiptTimeMillis */,
                 false /* cacheFlush */,
-                120000L /* ttlMillis */,
+                TEST_TTL,
                 textEntries);
-        response.setTextRecord(textRecord);
-        return response;
+        answerRecords.add(textRecord);
+        return new MdnsPacket(
+                0 /* flags */,
+                Collections.emptyList() /* questions */,
+                answerRecords,
+                Collections.emptyList() /* authorityRecords */,
+                Collections.emptyList() /* additionalRecords */
+        );
     }
 }
\ No newline at end of file
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
index 1d61cd3..abb1747 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
@@ -18,13 +18,11 @@
 
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
@@ -48,7 +46,6 @@
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -373,7 +370,7 @@
         mdnsClient.startDiscovery();
 
         verify(mockCallback, timeout(TIMEOUT).atLeast(1))
-                .onResponseReceived(any(MdnsResponse.class));
+                .onResponseReceived(any(MdnsPacket.class), anyInt(), any());
     }
 
     @Test
@@ -382,7 +379,7 @@
         mdnsClient.startDiscovery();
 
         verify(mockCallback, timeout(TIMEOUT).atLeastOnce())
-                .onResponseReceived(any(MdnsResponse.class));
+                .onResponseReceived(any(MdnsPacket.class), anyInt(), any());
 
         mdnsClient.stopDiscovery();
     }
@@ -415,7 +412,8 @@
         mdnsClient.startDiscovery();
 
         verify(mockCallback, timeout(TIMEOUT).atLeast(1))
-                .onFailedToParseMdnsResponse(anyInt(), eq(MdnsResponseErrorCode.ERROR_END_OF_FILE));
+                .onFailedToParseMdnsResponse(
+                        anyInt(), eq(MdnsResponseErrorCode.ERROR_END_OF_FILE), any());
 
         mdnsClient.stopDiscovery();
     }
@@ -436,7 +434,8 @@
         mdnsClient.startDiscovery();
 
         verify(mockCallback, timeout(TIMEOUT).atLeast(1))
-                .onFailedToParseMdnsResponse(1, MdnsResponseErrorCode.ERROR_END_OF_FILE);
+                .onFailedToParseMdnsResponse(
+                        eq(1), eq(MdnsResponseErrorCode.ERROR_END_OF_FILE), any());
 
         mdnsClient.stopDiscovery();
     }
@@ -514,7 +513,7 @@
         mdnsClient.startDiscovery();
 
         verify(mockCallback, timeout(TIMEOUT).atLeastOnce())
-                .onResponseReceived(argThat(response -> response.getInterfaceIndex() == 21));
+                .onResponseReceived(any(), eq(21), any());
     }
 
     @Test
@@ -536,11 +535,7 @@
         mdnsClient.setCallback(mockCallback);
         mdnsClient.startDiscovery();
 
-        ArgumentCaptor<MdnsResponse> mdnsResponseCaptor =
-                ArgumentCaptor.forClass(MdnsResponse.class);
         verify(mockMulticastSocket, never()).getInterfaceIndex();
-        verify(mockCallback, timeout(TIMEOUT).atLeast(1))
-                .onResponseReceived(mdnsResponseCaptor.capture());
-        assertEquals(-1, mdnsResponseCaptor.getValue().getInterfaceIndex());
+        verify(mockCallback, timeout(TIMEOUT).atLeast(1)).onResponseReceived(any(), eq(-1), any());
     }
 }
\ No newline at end of file
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
index 2bb61a6a..4b87556 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
@@ -16,6 +16,13 @@
 
 package com.android.server.connectivity.mdns;
 
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
+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 static com.android.testutils.ContextUtils.mockService;
 
 import static org.junit.Assert.assertEquals;
@@ -23,27 +30,41 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
-import android.net.INetd;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.TetheringManager;
 import android.net.TetheringManager.TetheringEventCallback;
 import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.system.OsConstants;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.net.module.util.ArrayTrackRecord;
+import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.netlink.NetlinkConstants;
+import com.android.net.module.util.netlink.RtNetlinkAddressMessage;
+import com.android.net.module.util.netlink.StructIfaddrMsg;
+import com.android.net.module.util.netlink.StructNlMsgHdr;
 import com.android.server.connectivity.mdns.MdnsSocketProvider.Dependencies;
+import com.android.server.connectivity.mdns.internal.SocketNetlinkMonitor;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 import com.android.testutils.HandlerUtils;
@@ -56,23 +77,29 @@
 import org.mockito.MockitoAnnotations;
 
 import java.io.IOException;
+import java.net.Inet6Address;
+import java.net.InetAddress;
 import java.util.Collections;
 import java.util.List;
 
 @RunWith(DevSdkIgnoreRunner.class)
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
 public class MdnsSocketProviderTest {
+    private static final String TAG = MdnsSocketProviderTest.class.getSimpleName();
     private static final String TEST_IFACE_NAME = "test";
     private static final String LOCAL_ONLY_IFACE_NAME = "local_only";
     private static final String TETHERED_IFACE_NAME = "tethered";
+    private static final int TETHERED_IFACE_IDX = 32;
     private static final long DEFAULT_TIMEOUT = 2000L;
     private static final long NO_CALLBACK_TIMEOUT = 200L;
     private static final LinkAddress LINKADDRV4 = new LinkAddress("192.0.2.0/24");
     private static final LinkAddress LINKADDRV6 =
             new LinkAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334/64");
-    private static final Network TEST_NETWORK = new Network(123);
-    private static final Network LOCAL_NETWORK = new Network(INetd.LOCAL_NET_ID);
 
+    private static final LinkAddress LINKADDRV6_FLAG_CHANGE =
+            new LinkAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334/64", 1 /* flags */,
+                    0 /* scope */);
+    private static final Network TEST_NETWORK = new Network(123);
     @Mock private Context mContext;
     @Mock private Dependencies mDeps;
     @Mock private ConnectivityManager mCm;
@@ -84,28 +111,58 @@
     private MdnsSocketProvider mSocketProvider;
     private NetworkCallback mNetworkCallback;
     private TetheringEventCallback mTetheringEventCallback;
+    private SharedLog mLog = new SharedLog("MdnsSocketProviderTest");
 
+    private TestNetlinkMonitor mTestSocketNetLinkMonitor;
     @Before
     public void setUp() throws IOException {
         MockitoAnnotations.initMocks(this);
         mockService(mContext, ConnectivityManager.class, Context.CONNECTIVITY_SERVICE, mCm);
+        if (mContext.getSystemService(ConnectivityManager.class) == null) {
+            // Test is using mockito-extended
+            doCallRealMethod().when(mContext).getSystemService(ConnectivityManager.class);
+        }
         mockService(mContext, TetheringManager.class, Context.TETHERING_SERVICE, mTm);
-        doReturn(true).when(mDeps).canScanOnInterface(any());
-        doReturn(mTestNetworkIfaceWrapper).when(mDeps).getNetworkInterfaceByName(TEST_IFACE_NAME);
+        if (mContext.getSystemService(TetheringManager.class) == null) {
+            // Test is using mockito-extended
+            doCallRealMethod().when(mContext).getSystemService(TetheringManager.class);
+        }
+        doReturn(mTestNetworkIfaceWrapper).when(mDeps).getNetworkInterfaceByName(anyString());
+        doReturn(true).when(mTestNetworkIfaceWrapper).isUp();
+        doReturn(true).when(mLocalOnlyIfaceWrapper).isUp();
+        doReturn(true).when(mTetheredIfaceWrapper).isUp();
+        doReturn(true).when(mTestNetworkIfaceWrapper).supportsMulticast();
+        doReturn(true).when(mLocalOnlyIfaceWrapper).supportsMulticast();
+        doReturn(true).when(mTetheredIfaceWrapper).supportsMulticast();
         doReturn(mLocalOnlyIfaceWrapper).when(mDeps)
                 .getNetworkInterfaceByName(LOCAL_ONLY_IFACE_NAME);
         doReturn(mTetheredIfaceWrapper).when(mDeps).getNetworkInterfaceByName(TETHERED_IFACE_NAME);
         doReturn(mock(MdnsInterfaceSocket.class))
-                .when(mDeps).createMdnsInterfaceSocket(any(), anyInt());
+                .when(mDeps).createMdnsInterfaceSocket(any(), anyInt(), any(), any());
+        doReturn(TETHERED_IFACE_IDX).when(mDeps).getNetworkInterfaceIndexByName(
+                TETHERED_IFACE_NAME);
         final HandlerThread thread = new HandlerThread("MdnsSocketProviderTest");
         thread.start();
         mHandler = new Handler(thread.getLooper());
 
+        doReturn(mTestSocketNetLinkMonitor).when(mDeps).createSocketNetlinkMonitor(any(), any(),
+                any());
+        doAnswer(inv -> {
+            mTestSocketNetLinkMonitor = new TestNetlinkMonitor(inv.getArgument(0),
+                    inv.getArgument(1),
+                    inv.getArgument(2));
+            return mTestSocketNetLinkMonitor;
+        }).when(mDeps).createSocketNetlinkMonitor(any(), any(),
+                any());
+        mSocketProvider = new MdnsSocketProvider(mContext, thread.getLooper(), mDeps, mLog);
+    }
+
+    private void startMonitoringSockets() {
         final ArgumentCaptor<NetworkCallback> nwCallbackCaptor =
                 ArgumentCaptor.forClass(NetworkCallback.class);
         final ArgumentCaptor<TetheringEventCallback> teCallbackCaptor =
                 ArgumentCaptor.forClass(TetheringEventCallback.class);
-        mSocketProvider = new MdnsSocketProvider(mContext, thread.getLooper(), mDeps);
+
         mHandler.post(mSocketProvider::startMonitoringSockets);
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
         verify(mCm).registerNetworkCallback(any(), nwCallbackCaptor.capture(), any());
@@ -113,6 +170,23 @@
 
         mNetworkCallback = nwCallbackCaptor.getValue();
         mTetheringEventCallback = teCallbackCaptor.getValue();
+
+        mHandler.post(mSocketProvider::startNetLinkMonitor);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+    }
+
+    private static class TestNetlinkMonitor extends SocketNetlinkMonitor {
+        TestNetlinkMonitor(@NonNull Handler handler,
+                @NonNull SharedLog log,
+                @Nullable MdnsSocketProvider.NetLinkMonitorCallBack cb) {
+            super(handler, log, cb);
+        }
+
+        @Override
+        public void startMonitoring() { }
+
+        @Override
+        public void stopMonitoring() { }
     }
 
     private class TestSocketCallback implements MdnsSocketProvider.SocketCallback {
@@ -159,12 +233,13 @@
         }
 
         @Override
-        public void onAddressesChanged(Network network, List<LinkAddress> addresses) {
+        public void onAddressesChanged(Network network, MdnsInterfaceSocket socket,
+                List<LinkAddress> addresses) {
             mHistory.add(new AddressesChangedEvent(network, addresses));
         }
 
         public void expectedSocketCreatedForNetwork(Network network, List<LinkAddress> addresses) {
-            final SocketEvent event = mHistory.poll(DEFAULT_TIMEOUT, c -> true);
+            final SocketEvent event = mHistory.poll(0L /* timeoutMs */, c -> true);
             assertNotNull(event);
             assertTrue(event instanceof SocketCreatedEvent);
             assertEquals(network, event.mNetwork);
@@ -172,7 +247,7 @@
         }
 
         public void expectedInterfaceDestroyedForNetwork(Network network) {
-            final SocketEvent event = mHistory.poll(DEFAULT_TIMEOUT, c -> true);
+            final SocketEvent event = mHistory.poll(0L /* timeoutMs */, c -> true);
             assertNotNull(event);
             assertTrue(event instanceof InterfaceDestroyedEvent);
             assertEquals(network, event.mNetwork);
@@ -180,7 +255,7 @@
 
         public void expectedAddressesChangedForNetwork(Network network,
                 List<LinkAddress> addresses) {
-            final SocketEvent event = mHistory.poll(DEFAULT_TIMEOUT, c -> true);
+            final SocketEvent event = mHistory.poll(0L /* timeoutMs */, c -> true);
             assertNotNull(event);
             assertTrue(event instanceof AddressesChangedEvent);
             assertEquals(network, event.mNetwork);
@@ -193,19 +268,34 @@
         }
     }
 
+    private static NetworkCapabilities makeCapabilities(int... transports) {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        for (int transport : transports) {
+            nc.addTransportType(transport);
+        }
+        return nc;
+    }
+
+    private void postNetworkAvailable(int... transports) {
+        final LinkProperties testLp = new LinkProperties();
+        testLp.setInterfaceName(TEST_IFACE_NAME);
+        testLp.setLinkAddresses(List.of(LINKADDRV4));
+        final NetworkCapabilities testNc = makeCapabilities(transports);
+        mHandler.post(() -> mNetworkCallback.onCapabilitiesChanged(TEST_NETWORK, testNc));
+        mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, testLp));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+    }
+
     @Test
     public void testSocketRequestAndUnrequestSocket() {
+        startMonitoringSockets();
+
         final TestSocketCallback testCallback1 = new TestSocketCallback();
         mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback1));
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
         testCallback1.expectedNoCallback();
 
-        final LinkProperties testLp = new LinkProperties();
-        testLp.setInterfaceName(TEST_IFACE_NAME);
-        testLp.setLinkAddresses(List.of(LINKADDRV4));
-        mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, testLp));
-        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
-        verify(mTestNetworkIfaceWrapper).getNetworkInterface();
+        postNetworkAvailable(TRANSPORT_WIFI);
         testCallback1.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
 
         final TestSocketCallback testCallback2 = new TestSocketCallback();
@@ -227,7 +317,7 @@
         verify(mLocalOnlyIfaceWrapper).getNetworkInterface();
         testCallback1.expectedNoCallback();
         testCallback2.expectedNoCallback();
-        testCallback3.expectedSocketCreatedForNetwork(LOCAL_NETWORK, List.of());
+        testCallback3.expectedSocketCreatedForNetwork(null /* network */, List.of());
 
         mHandler.post(() -> mTetheringEventCallback.onTetheredInterfacesChanged(
                 List.of(TETHERED_IFACE_NAME)));
@@ -235,7 +325,7 @@
         verify(mTetheredIfaceWrapper).getNetworkInterface();
         testCallback1.expectedNoCallback();
         testCallback2.expectedNoCallback();
-        testCallback3.expectedSocketCreatedForNetwork(LOCAL_NETWORK, List.of());
+        testCallback3.expectedSocketCreatedForNetwork(null /* network */, List.of());
 
         mHandler.post(() -> mSocketProvider.unrequestSocket(testCallback1));
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
@@ -253,22 +343,178 @@
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
         testCallback1.expectedNoCallback();
         testCallback2.expectedNoCallback();
-        testCallback3.expectedInterfaceDestroyedForNetwork(LOCAL_NETWORK);
+        testCallback3.expectedInterfaceDestroyedForNetwork(null /* network */);
 
         mHandler.post(() -> mSocketProvider.unrequestSocket(testCallback3));
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
         testCallback1.expectedNoCallback();
         testCallback2.expectedNoCallback();
+        // There was still a tethered interface, but no callback should be sent once unregistered
         testCallback3.expectedNoCallback();
     }
 
+    private RtNetlinkAddressMessage createNetworkAddressUpdateNetLink(
+            short msgType, LinkAddress linkAddress, int ifIndex, int flags) {
+        final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
+        nlmsghdr.nlmsg_type = msgType;
+        nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+        nlmsghdr.nlmsg_seq = 1;
+
+        InetAddress ip = linkAddress.getAddress();
+
+        final byte family =
+                (byte) ((ip instanceof Inet6Address) ? OsConstants.AF_INET6 : OsConstants.AF_INET);
+        StructIfaddrMsg structIfaddrMsg = new StructIfaddrMsg(family,
+                (short) linkAddress.getPrefixLength(),
+                (short) linkAddress.getFlags(), (short) linkAddress.getScope(), ifIndex);
+
+        return new RtNetlinkAddressMessage(nlmsghdr, structIfaddrMsg, ip,
+                null /* structIfacacheInfo */, flags);
+    }
+
+    @Test
+    public void testDownstreamNetworkAddressUpdateFromNetlink() {
+        startMonitoringSockets();
+        final TestSocketCallback testCallbackAll = new TestSocketCallback();
+        mHandler.post(() -> mSocketProvider.requestSocket(null /* network */, testCallbackAll));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+
+        // Address add message arrived before the interface is created.
+        RtNetlinkAddressMessage addIpv4AddrMsg = createNetworkAddressUpdateNetLink(
+                NetlinkConstants.RTM_NEWADDR,
+                LINKADDRV4,
+                TETHERED_IFACE_IDX,
+                0 /* flags */);
+        mHandler.post(
+                () -> mTestSocketNetLinkMonitor.processNetlinkMessage(addIpv4AddrMsg,
+                        0 /* whenMs */));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+
+        // Interface is created.
+        mHandler.post(() -> mTetheringEventCallback.onTetheredInterfacesChanged(
+                List.of(TETHERED_IFACE_NAME)));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mTetheredIfaceWrapper).getNetworkInterface();
+        testCallbackAll.expectedSocketCreatedForNetwork(null /* network */, List.of(LINKADDRV4));
+
+        // Old Address removed.
+        RtNetlinkAddressMessage removeIpv4AddrMsg = createNetworkAddressUpdateNetLink(
+                NetlinkConstants.RTM_DELADDR,
+                LINKADDRV4,
+                TETHERED_IFACE_IDX,
+                0 /* flags */);
+        mHandler.post(
+                () -> mTestSocketNetLinkMonitor.processNetlinkMessage(removeIpv4AddrMsg,
+                        0 /* whenMs */));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        testCallbackAll.expectedAddressesChangedForNetwork(null /* network */, List.of());
+
+        // New address added.
+        RtNetlinkAddressMessage addIpv6AddrMsg = createNetworkAddressUpdateNetLink(
+                NetlinkConstants.RTM_NEWADDR,
+                LINKADDRV6,
+                TETHERED_IFACE_IDX,
+                0 /* flags */);
+        mHandler.post(() -> mTestSocketNetLinkMonitor.processNetlinkMessage(addIpv6AddrMsg,
+                0 /* whenMs */));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        testCallbackAll.expectedAddressesChangedForNetwork(null /* network */, List.of(LINKADDRV6));
+
+        // Address updated
+        RtNetlinkAddressMessage updateIpv6AddrMsg = createNetworkAddressUpdateNetLink(
+                NetlinkConstants.RTM_NEWADDR,
+                LINKADDRV6,
+                TETHERED_IFACE_IDX,
+                1 /* flags */);
+        mHandler.post(
+                () -> mTestSocketNetLinkMonitor.processNetlinkMessage(updateIpv6AddrMsg,
+                        0 /* whenMs */));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        testCallbackAll.expectedAddressesChangedForNetwork(null /* network */,
+                List.of(LINKADDRV6_FLAG_CHANGE));
+    }
+
     @Test
     public void testAddressesChanged() throws Exception {
+        startMonitoringSockets();
+
         final TestSocketCallback testCallback = new TestSocketCallback();
         mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
         testCallback.expectedNoCallback();
 
+        postNetworkAvailable(TRANSPORT_WIFI);
+        testCallback.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
+
+        final LinkProperties newTestLp = new LinkProperties();
+        newTestLp.setInterfaceName(TEST_IFACE_NAME);
+        newTestLp.setLinkAddresses(List.of(LINKADDRV4, LINKADDRV6));
+        mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, newTestLp));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        testCallback.expectedAddressesChangedForNetwork(
+                TEST_NETWORK, List.of(LINKADDRV4, LINKADDRV6));
+    }
+
+    @Test
+    public void testStartAndStopMonitoringSockets() {
+        // Stop monitoring sockets before start. Should not unregister any network callback.
+        mHandler.post(mSocketProvider::requestStopWhenInactive);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mCm, never()).unregisterNetworkCallback(any(NetworkCallback.class));
+        verify(mTm, never()).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
+
+        // Start sockets monitoring.
+        startMonitoringSockets();
+        // Request a socket then unrequest it. Expect no network callback unregistration.
+        final TestSocketCallback testCallback = new TestSocketCallback();
+        mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        testCallback.expectedNoCallback();
+        mHandler.post(()-> mSocketProvider.unrequestSocket(testCallback));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mCm, never()).unregisterNetworkCallback(any(NetworkCallback.class));
+        verify(mTm, never()).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
+        // Request stop and it should unregister network callback immediately because there is no
+        // socket request.
+        mHandler.post(mSocketProvider::requestStopWhenInactive);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mCm, times(1)).unregisterNetworkCallback(any(NetworkCallback.class));
+        verify(mTm, times(1)).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
+
+        // Start sockets monitoring and request a socket again.
+        mHandler.post(mSocketProvider::startMonitoringSockets);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mCm, times(2)).registerNetworkCallback(any(), any(NetworkCallback.class), any());
+        verify(mTm, times(2)).registerTetheringEventCallback(
+                any(), any(TetheringEventCallback.class));
+        final TestSocketCallback testCallback2 = new TestSocketCallback();
+        mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback2));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        testCallback2.expectedNoCallback();
+        // Try to stop monitoring sockets but should be ignored and wait until all socket are
+        // unrequested.
+        mHandler.post(mSocketProvider::requestStopWhenInactive);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mCm, times(1)).unregisterNetworkCallback(any(NetworkCallback.class));
+        verify(mTm, times(1)).unregisterTetheringEventCallback(any());
+        // Unrequest the socket then network callbacks should be unregistered.
+        mHandler.post(()-> mSocketProvider.unrequestSocket(testCallback2));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mCm, times(2)).unregisterNetworkCallback(any(NetworkCallback.class));
+        verify(mTm, times(2)).unregisterTetheringEventCallback(any(TetheringEventCallback.class));
+    }
+
+    @Test
+    public void testLinkPropertiesAreClearedAfterStopMonitoringSockets() {
+        startMonitoringSockets();
+
+        // Request a socket with null network.
+        final TestSocketCallback testCallback = new TestSocketCallback();
+        mHandler.post(() -> mSocketProvider.requestSocket(null, testCallback));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        testCallback.expectedNoCallback();
+
+        // Notify a LinkPropertiesChanged with TEST_NETWORK.
         final LinkProperties testLp = new LinkProperties();
         testLp.setInterfaceName(TEST_IFACE_NAME);
         testLp.setLinkAddresses(List.of(LINKADDRV4));
@@ -277,13 +523,109 @@
         verify(mTestNetworkIfaceWrapper, times(1)).getNetworkInterface();
         testCallback.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
 
-        final LinkProperties newTestLp = new LinkProperties();
-        newTestLp.setInterfaceName(TEST_IFACE_NAME);
-        newTestLp.setLinkAddresses(List.of(LINKADDRV4, LINKADDRV6));
-        mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, newTestLp));
+        // Try to stop monitoring and unrequest the socket.
+        mHandler.post(mSocketProvider::requestStopWhenInactive);
         HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
-        verify(mTestNetworkIfaceWrapper, times(1)).getNetworkInterface();
-        testCallback.expectedAddressesChangedForNetwork(
-                TEST_NETWORK, List.of(LINKADDRV4, LINKADDRV6));
+        mHandler.post(()-> mSocketProvider.unrequestSocket(testCallback));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        // No callback sent when unregistered
+        testCallback.expectedNoCallback();
+        verify(mCm, times(1)).unregisterNetworkCallback(any(NetworkCallback.class));
+        verify(mTm, times(1)).unregisterTetheringEventCallback(any());
+
+        // Start sockets monitoring and request a socket again. Expected no socket created callback
+        // because all saved LinkProperties has been cleared.
+        mHandler.post(mSocketProvider::startMonitoringSockets);
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mCm, times(2)).registerNetworkCallback(any(), any(NetworkCallback.class), any());
+        verify(mTm, times(2)).registerTetheringEventCallback(
+                any(), any(TetheringEventCallback.class));
+        mHandler.post(() -> mSocketProvider.requestSocket(null, testCallback));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        testCallback.expectedNoCallback();
+
+        // Notify a LinkPropertiesChanged with another network.
+        final LinkProperties otherLp = new LinkProperties();
+        final LinkAddress otherAddress = new LinkAddress("192.0.2.1/24");
+        final Network otherNetwork = new Network(456);
+        otherLp.setInterfaceName("test2");
+        otherLp.setLinkAddresses(List.of(otherAddress));
+        mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(otherNetwork, otherLp));
+        HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+        verify(mTestNetworkIfaceWrapper, times(2)).getNetworkInterface();
+        testCallback.expectedSocketCreatedForNetwork(otherNetwork, List.of(otherAddress));
+    }
+
+    @Test
+    public void testNoSocketCreatedForCellular() {
+        startMonitoringSockets();
+
+        final TestSocketCallback testCallback = new TestSocketCallback();
+        mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+
+        postNetworkAvailable(TRANSPORT_CELLULAR);
+        testCallback.expectedNoCallback();
+    }
+
+    @Test
+    public void testNoSocketCreatedForNonMulticastInterface() throws Exception {
+        doReturn(false).when(mTestNetworkIfaceWrapper).supportsMulticast();
+        startMonitoringSockets();
+
+        final TestSocketCallback testCallback = new TestSocketCallback();
+        mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+
+        postNetworkAvailable(TRANSPORT_BLUETOOTH);
+        testCallback.expectedNoCallback();
+    }
+
+    @Test
+    public void testSocketCreatedForMulticastInterface() throws Exception {
+        doReturn(true).when(mTestNetworkIfaceWrapper).supportsMulticast();
+        startMonitoringSockets();
+
+        final TestSocketCallback testCallback = new TestSocketCallback();
+        mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+
+        postNetworkAvailable(TRANSPORT_BLUETOOTH);
+        testCallback.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
+    }
+
+    @Test
+    public void testNoSocketCreatedForPTPInterface() throws Exception {
+        doReturn(true).when(mTestNetworkIfaceWrapper).isPointToPoint();
+        startMonitoringSockets();
+
+        final TestSocketCallback testCallback = new TestSocketCallback();
+        mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+
+        postNetworkAvailable(TRANSPORT_BLUETOOTH);
+        testCallback.expectedNoCallback();
+    }
+
+    @Test
+    public void testNoSocketCreatedForVPNInterface() throws Exception {
+        // VPN interfaces generally also have IFF_POINTOPOINT, but even if they don't, they should
+        // not be included even with TRANSPORT_WIFI.
+        doReturn(false).when(mTestNetworkIfaceWrapper).supportsMulticast();
+        startMonitoringSockets();
+
+        final TestSocketCallback testCallback = new TestSocketCallback();
+        mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+
+        postNetworkAvailable(TRANSPORT_VPN, TRANSPORT_WIFI);
+        testCallback.expectedNoCallback();
+    }
+
+    @Test
+    public void testSocketCreatedForWifiWithoutMulticastFlag() throws Exception {
+        doReturn(false).when(mTestNetworkIfaceWrapper).supportsMulticast();
+        startMonitoringSockets();
+
+        final TestSocketCallback testCallback = new TestSocketCallback();
+        mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+
+        postNetworkAvailable(TRANSPORT_WIFI);
+        testCallback.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
     }
 }
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitorTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitorTest.kt
new file mode 100644
index 0000000..c62a081
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitorTest.kt
@@ -0,0 +1,90 @@
+package com.android.server.connectivity.mdns.internal
+
+import android.net.LinkAddress
+import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
+import android.system.OsConstants
+import com.android.net.module.util.SharedLog
+import com.android.net.module.util.netlink.NetlinkConstants
+import com.android.net.module.util.netlink.RtNetlinkAddressMessage
+import com.android.net.module.util.netlink.StructIfaddrMsg
+import com.android.net.module.util.netlink.StructNlMsgHdr
+import com.android.server.connectivity.mdns.MdnsAdvertiserTest
+import com.android.server.connectivity.mdns.MdnsSocketProvider
+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.ArgumentMatchers.eq
+import org.mockito.Mockito
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+private val LINKADDRV4 = LinkAddress("192.0.2.0/24")
+private val IFACE_IDX = 32
+
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+internal class SocketNetlinkMonitorTest {
+    private val thread = HandlerThread(MdnsAdvertiserTest::class.simpleName)
+    private val sharedlog = Mockito.mock(SharedLog::class.java)
+    private val netlinkMonitorCallBack =
+            Mockito.mock(MdnsSocketProvider.NetLinkMonitorCallBack::class.java)
+
+    @Before
+    fun setUp() {
+        thread.start()
+    }
+
+    @After
+    fun tearDown() {
+        thread.quitSafely()
+    }
+
+    @Test
+    fun testHandleDeprecatedNetlinkMessage() {
+        val socketNetlinkMonitor = SocketNetlinkMonitor(Handler(thread.looper), sharedlog,
+                netlinkMonitorCallBack)
+        val nlmsghdr = StructNlMsgHdr().apply {
+            nlmsg_type = NetlinkConstants.RTM_NEWADDR
+            nlmsg_flags = (StructNlMsgHdr.NLM_F_REQUEST.toInt()
+                    or StructNlMsgHdr.NLM_F_ACK.toInt()).toShort()
+            nlmsg_seq = 1
+        }
+        val structIfaddrMsg = StructIfaddrMsg(OsConstants.AF_INET.toShort(),
+                LINKADDRV4.prefixLength.toShort(),
+                LINKADDRV4.flags.toShort(),
+                LINKADDRV4.scope.toShort(), IFACE_IDX)
+        // If the LinkAddress is not preferred, RTM_NEWADDR will not trigger
+        // addOrUpdateInterfaceAddress() callback.
+        val deprecatedAddNetLinkMessage = RtNetlinkAddressMessage(nlmsghdr, structIfaddrMsg,
+            LINKADDRV4.address, null /* structIfacacheInfo */,
+            LINKADDRV4.flags or OsConstants.IFA_F_DEPRECATED)
+        socketNetlinkMonitor.processNetlinkMessage(deprecatedAddNetLinkMessage, 0L /* whenMs */)
+        verify(netlinkMonitorCallBack, never()).addOrUpdateInterfaceAddress(eq(IFACE_IDX),
+                 argThat { it.address == LINKADDRV4.address })
+
+        // If the LinkAddress is preferred, RTM_NEWADDR will trigger addOrUpdateInterfaceAddress()
+        // callback.
+        val preferredAddNetLinkMessage = RtNetlinkAddressMessage(nlmsghdr, structIfaddrMsg,
+            LINKADDRV4.address, null /* structIfacacheInfo */,
+            LINKADDRV4.flags or OsConstants.IFA_F_OPTIMISTIC)
+        socketNetlinkMonitor.processNetlinkMessage(preferredAddNetLinkMessage, 0L /* whenMs */)
+        verify(netlinkMonitorCallBack).addOrUpdateInterfaceAddress(eq(IFACE_IDX),
+            argThat { it.address == LINKADDRV4.address })
+
+        // Even if the LinkAddress is not preferred, RTM_DELADDR will trigger
+        // deleteInterfaceAddress() callback.
+        nlmsghdr.nlmsg_type = NetlinkConstants.RTM_DELADDR
+        val deprecatedDelNetLinkMessage = RtNetlinkAddressMessage(nlmsghdr, structIfaddrMsg,
+            LINKADDRV4.address, null /* structIfacacheInfo */,
+            LINKADDRV4.flags or OsConstants.IFA_F_DEPRECATED)
+        socketNetlinkMonitor.processNetlinkMessage(deprecatedDelNetLinkMessage, 0L /* whenMs */)
+        verify(netlinkMonitorCallBack).deleteInterfaceAddress(eq(IFACE_IDX),
+                argThat { it.address == LINKADDRV4.address })
+    }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
new file mode 100644
index 0000000..f705bcb
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns.util
+
+import android.os.Build
+import com.android.server.connectivity.mdns.util.MdnsUtils.equalsDnsLabelIgnoreDnsCase
+import com.android.server.connectivity.mdns.util.MdnsUtils.equalsIgnoreDnsCase
+import com.android.server.connectivity.mdns.util.MdnsUtils.toDnsLabelsLowerCase
+import com.android.server.connectivity.mdns.util.MdnsUtils.toDnsLowerCase
+import com.android.server.connectivity.mdns.util.MdnsUtils.truncateServiceName
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class MdnsUtilsTest {
+    @Test
+    fun testToDnsLowerCase() {
+        assertEquals("test", toDnsLowerCase("TEST"))
+        assertEquals("test", toDnsLowerCase("TeSt"))
+        assertEquals("test", toDnsLowerCase("test"))
+        assertEquals("tÉst", toDnsLowerCase("TÉST"))
+        assertEquals("ţést", toDnsLowerCase("ţést"))
+        // Unicode characters 0x10000 (𐀀), 0x10001 (𐀁), 0x10041 (𐁁)
+        // Note the last 2 bytes of 0x10041 are identical to 'A', but it should remain unchanged.
+        assertEquals("test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- ",
+                toDnsLowerCase("Test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- "))
+        // Also test some characters where the first surrogate is not \ud800
+        assertEquals("test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
+                "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<",
+                toDnsLowerCase("Test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
+                        "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<"))
+    }
+
+    @Test
+    fun testToDnsLabelsLowerCase() {
+        assertArrayEquals(arrayOf("test", "tÉst", "ţést"),
+            toDnsLabelsLowerCase(arrayOf("TeSt", "TÉST", "ţést")))
+    }
+
+    @Test
+    fun testEqualsIgnoreDnsCase() {
+        assertTrue(equalsIgnoreDnsCase("TEST", "Test"))
+        assertTrue(equalsIgnoreDnsCase("TEST", "test"))
+        assertTrue(equalsIgnoreDnsCase("test", "TeSt"))
+        assertTrue(equalsIgnoreDnsCase("Tést", "tést"))
+        assertFalse(equalsIgnoreDnsCase("ŢÉST", "ţést"))
+        // Unicode characters 0x10000 (𐀀), 0x10001 (𐀁), 0x10041 (𐁁)
+        // Note the last 2 bytes of 0x10041 are identical to 'A', but it should remain unchanged.
+        assertTrue(equalsIgnoreDnsCase("test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- ",
+                "Test: -->\ud800\udc00 \ud800\udc01 \ud800\udc41<-- "))
+        // Also test some characters where the first surrogate is not \ud800
+        assertTrue(equalsIgnoreDnsCase("test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
+                "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<",
+                "Test: >\ud83c\udff4\udb40\udc67\udb40\udc62\udb40" +
+                        "\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f<"))
+    }
+
+    @Test
+    fun testTruncateServiceName() {
+        assertEquals(truncateServiceName("测试abcde", 7), "测试a")
+        assertEquals(truncateServiceName("测试abcde", 100), "测试abcde")
+    }
+
+    @Test
+    fun testEqualsLabelIgnoreDnsCase() {
+        assertTrue(equalsDnsLabelIgnoreDnsCase(arrayOf("TEST", "Test"), arrayOf("test", "test")))
+        assertFalse(equalsDnsLabelIgnoreDnsCase(arrayOf("TEST", "Test"), arrayOf("test")))
+        assertFalse(equalsDnsLabelIgnoreDnsCase(arrayOf("Test"), arrayOf("test", "test")))
+        assertFalse(equalsDnsLabelIgnoreDnsCase(arrayOf("TEST", "Test"), arrayOf("test", "tést")))
+    }
+
+    @Test
+    fun testTypeEqualsOrIsSubtype() {
+        assertTrue(MdnsUtils.typeEqualsOrIsSubtype(arrayOf("_type", "_tcp", "local"),
+            arrayOf("_type", "_TCP", "local")))
+        assertTrue(MdnsUtils.typeEqualsOrIsSubtype(arrayOf("_type", "_tcp", "local"),
+            arrayOf("a", "_SUB", "_type", "_TCP", "local")))
+        assertFalse(MdnsUtils.typeEqualsOrIsSubtype(arrayOf("_sub", "_type", "_tcp", "local"),
+                arrayOf("_type", "_TCP", "local")))
+        assertFalse(MdnsUtils.typeEqualsOrIsSubtype(
+                arrayOf("a", "_other", "_type", "_tcp", "local"),
+                arrayOf("a", "_SUB", "_type", "_TCP", "local")))
+    }
+}
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
index 5e7f0ff..e6aba22 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
@@ -86,8 +86,9 @@
     }
 
     @After
-    public void cleanUp() {
+    public void cleanUp() throws InterruptedException {
         mHandlerThread.quitSafely();
+        mHandlerThread.join();
     }
 
     private void initMockResources() {
diff --git a/tests/unit/java/com/android/server/net/IpConfigStoreTest.java b/tests/unit/java/com/android/server/net/IpConfigStoreTest.java
index 4adc999..dcf0f75 100644
--- a/tests/unit/java/com/android/server/net/IpConfigStoreTest.java
+++ b/tests/unit/java/com/android/server/net/IpConfigStoreTest.java
@@ -36,7 +36,6 @@
 
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
-import com.android.testutils.HandlerUtils;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -58,7 +57,7 @@
 @RunWith(DevSdkIgnoreRunner.class)
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
 public class IpConfigStoreTest {
-    private static final int TIMEOUT_MS = 2_000;
+    private static final int TIMEOUT_MS = 5_000;
     private static final int KEY_CONFIG = 17;
     private static final String IFACE_1 = "eth0";
     private static final String IFACE_2 = "eth1";
@@ -139,6 +138,8 @@
                     }
                     @Override
                     public void quitHandlerThread(HandlerThread handlerThread) {
+                        // Don't join in here, quitHandlerThread runs on the
+                        // handler thread itself.
                         testHandlerThread.quitSafely();
                     }
         };
@@ -155,7 +156,7 @@
         final DelayedDiskWrite writer = new DelayedDiskWrite(dependencies);
         final IpConfigStore store = new IpConfigStore(writer);
         store.writeIpConfigurations(configFile.getPath(), expectedNetworks);
-        HandlerUtils.waitForIdle(testHandlerThread, TIMEOUT_MS);
+        testHandlerThread.join();
 
         // Read IP config from the file path.
         final ArrayMap<String, IpConfiguration> actualNetworks =
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 04db6d3..63daebc 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -472,8 +472,7 @@
                         256L, 16L, 512L, 32L, 0L)
                 .insertEntry(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 64L, 3L, 1024L, 8L, 0L);
 
-        doReturn(stats).when(mDeps).getNetworkStatsDetail(anyInt(), any(),
-                anyInt());
+        doReturn(stats).when(mDeps).getNetworkStatsDetail();
 
         final String[] ifaces = new String[]{TEST_IFACE};
         final NetworkStats res = mFactory.readNetworkStatsDetail(UID_ALL, ifaces, TAG_ALL);
@@ -488,8 +487,7 @@
         mFactory.removeUidsLocked(removedUids);
 
         // Return empty stats for reading the result of removing uids stats later.
-        doReturn(buildEmptyStats()).when(mDeps).getNetworkStatsDetail(anyInt(), any(),
-                anyInt());
+        doReturn(buildEmptyStats()).when(mDeps).getNetworkStatsDetail();
 
         final NetworkStats removedUidsStats =
                 mFactory.readNetworkStatsDetail(UID_ALL, ifaces, TAG_ALL);
@@ -574,8 +572,7 @@
         final NetworkStats statsFromResource = parseNetworkStatsFromGoldenSample(resourceId,
                 24 /* initialSize */, true /* consumeHeader */, false /* checkActive */,
                 true /* isUidData */);
-        doReturn(statsFromResource).when(mDeps).getNetworkStatsDetail(anyInt(), any(),
-                anyInt());
+        doReturn(statsFromResource).when(mDeps).getNetworkStatsDetail();
         return mFactory.readNetworkStatsDetail();
     }
 
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index d7c90d8..b8b0289 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -82,7 +82,6 @@
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -97,7 +96,6 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.database.ContentObserver;
-import android.net.ConnectivityResources;
 import android.net.DataUsageRequest;
 import android.net.INetd;
 import android.net.INetworkStatsSession;
@@ -146,6 +144,7 @@
 import com.android.net.module.util.bpf.CookieTagMapKey;
 import com.android.net.module.util.bpf.CookieTagMapValue;
 import com.android.server.BpfNetMaps;
+import com.android.server.connectivity.ConnectivityResources;
 import com.android.server.net.NetworkStatsService.AlertObserver;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
@@ -395,10 +394,6 @@
         verify(mNetd).registerUnsolicitedEventListener(alertObserver.capture());
         mAlertObserver = alertObserver.getValue();
 
-        // Make augmentWithStackedInterfaces returns the interfaces that was passed to it.
-        doAnswer(inv -> ((String[]) inv.getArgument(0)).clone())
-                .when(mStatsFactory).augmentWithStackedInterfaces(any());
-
         // Catch TetheringEventCallback during systemReady().
         ArgumentCaptor<TetheringManager.TetheringEventCallback> tetheringEventCbCaptor =
                 ArgumentCaptor.forClass(TetheringManager.TetheringEventCallback.class);
@@ -544,6 +539,7 @@
         mService = null;
 
         mHandlerThread.quitSafely();
+        mHandlerThread.join();
     }
 
     private void initWifiStats(NetworkStateSnapshot snapshot) throws Exception {
@@ -1930,12 +1926,17 @@
         // Templates w/o wifi network keys can query stats as usual.
         assertNetworkTotal(sTemplateCarrierWifi1, 0L, 0L, 0L, 0L, 0);
         assertNetworkTotal(sTemplateImsi1, 0L, 0L, 0L, 0L, 0);
+        // Templates for test network does not need to enforce location permission.
+        final NetworkTemplate templateTestIface1 = new NetworkTemplate.Builder(MATCH_TEST)
+                .setWifiNetworkKeys(Set.of(TEST_IFACE)).build();
+        assertNetworkTotal(templateTestIface1, 0L, 0L, 0L, 0L, 0);
 
         doReturn(true).when(mLocationPermissionChecker)
                 .checkCallersLocationPermission(any(), any(), anyInt(), anyBoolean(), any());
         assertNetworkTotal(sTemplateCarrierWifi1, 0L, 0L, 0L, 0L, 0);
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
         assertNetworkTotal(sTemplateImsi1, 0L, 0L, 0L, 0L, 0);
+        assertNetworkTotal(templateTestIface1, 0L, 0L, 0L, 0L, 0);
     }
 
     /**
@@ -2198,7 +2199,7 @@
 
     private NetworkStatsCollection getLegacyCollection(String prefix, boolean includeTags) {
         final NetworkStatsRecorder recorder = makeTestRecorder(mLegacyStatsDir, prefix,
-                mSettings.getDevConfig(), includeTags, false);
+                mSettings.getXtConfig(), includeTags, false);
         return recorder.getOrLoadCompleteLocked();
     }
 
@@ -2255,14 +2256,9 @@
     }
 
     private void mockNetworkStatsSummary(NetworkStats summary) throws Exception {
-        mockNetworkStatsSummaryDev(summary.clone());
         mockNetworkStatsSummaryXt(summary.clone());
     }
 
-    private void mockNetworkStatsSummaryDev(NetworkStats summary) throws Exception {
-        doReturn(summary).when(mStatsFactory).readNetworkStatsSummaryDev();
-    }
-
     private void mockNetworkStatsSummaryXt(NetworkStats summary) throws Exception {
         doReturn(summary).when(mStatsFactory).readNetworkStatsSummaryXt();
     }
@@ -2293,13 +2289,11 @@
         doReturn(false).when(mSettings).getCombineSubtypeEnabled();
 
         final Config config = new Config(bucketDuration, deleteAge, deleteAge);
-        doReturn(config).when(mSettings).getDevConfig();
         doReturn(config).when(mSettings).getXtConfig();
         doReturn(config).when(mSettings).getUidConfig();
         doReturn(config).when(mSettings).getUidTagConfig();
 
         doReturn(MB_IN_BYTES).when(mSettings).getGlobalAlertBytes(anyLong());
-        doReturn(MB_IN_BYTES).when(mSettings).getDevPersistBytes(anyLong());
         doReturn(MB_IN_BYTES).when(mSettings).getXtPersistBytes(anyLong());
         doReturn(MB_IN_BYTES).when(mSettings).getUidPersistBytes(anyLong());
         doReturn(MB_IN_BYTES).when(mSettings).getUidTagPersistBytes(anyLong());
diff --git a/tests/unit/res/xml/self_certified_capabilities_bandwidth.xml b/tests/unit/res/xml/self_certified_capabilities_bandwidth.xml
new file mode 100644
index 0000000..01fdca1
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_bandwidth.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+</network-capabilities-declaration>
diff --git a/tests/unit/res/xml/self_certified_capabilities_both.xml b/tests/unit/res/xml/self_certified_capabilities_both.xml
new file mode 100644
index 0000000..4066be2
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_both.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+</network-capabilities-declaration>
diff --git a/tests/unit/res/xml/self_certified_capabilities_latency.xml b/tests/unit/res/xml/self_certified_capabilities_latency.xml
new file mode 100644
index 0000000..1c4a0e0
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_latency.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+</network-capabilities-declaration>
diff --git a/tests/unit/res/xml/self_certified_capabilities_other.xml b/tests/unit/res/xml/self_certified_capabilities_other.xml
new file mode 100644
index 0000000..5b6649c
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_other.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="other"/>
+</network-capabilities-declaration>
diff --git a/tests/unit/res/xml/self_certified_capabilities_wrong_declaration.xml b/tests/unit/res/xml/self_certified_capabilities_wrong_declaration.xml
new file mode 100644
index 0000000..6082356
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_wrong_declaration.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<network-capabilities-declaration1 xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+</network-capabilities-declaration1>
diff --git a/tests/unit/res/xml/self_certified_capabilities_wrong_tag.xml b/tests/unit/res/xml/self_certified_capabilities_wrong_tag.xml
new file mode 100644
index 0000000..c9ecc0b
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_wrong_tag.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability1 android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+</network-capabilities-declaration>
diff --git a/tests/unit/vpn-jarjar-rules.txt b/tests/unit/vpn-jarjar-rules.txt
index 16661b9..1a6bddc 100644
--- a/tests/unit/vpn-jarjar-rules.txt
+++ b/tests/unit/vpn-jarjar-rules.txt
@@ -1,4 +1,4 @@
 # Only keep classes imported by ConnectivityServiceTest
-keep com.android.server.VpnManagerService
 keep com.android.server.connectivity.Vpn
-keep com.android.server.connectivity.VpnProfileStore
\ No newline at end of file
+keep com.android.server.connectivity.VpnProfileStore
+keep com.android.server.net.LockdownVpnTracker
diff --git a/tools/gen_jarjar.py b/tools/gen_jarjar.py
index eb686ce..5129128 100755
--- a/tools/gen_jarjar.py
+++ b/tools/gen_jarjar.py
@@ -120,9 +120,11 @@
                         _get_toplevel_class(clazz) not in excluded_classes and
                         not any(r.fullmatch(clazz) for r in exclude_regexes)):
                     outfile.write(f'rule {clazz} {args.prefix}.@0\n')
-                    # Also include jarjar rules for unit tests of the class, so the package matches
-                    outfile.write(f'rule {clazz}Test {args.prefix}.@0\n')
-                    outfile.write(f'rule {clazz}Test$* {args.prefix}.@0\n')
+                    # Also include jarjar rules for unit tests of the class if it's not explicitly
+                    # excluded, so the package matches
+                    if not any(r.fullmatch(clazz + 'Test') for r in exclude_regexes):
+                        outfile.write(f'rule {clazz}Test {args.prefix}.@0\n')
+                        outfile.write(f'rule {clazz}Test$* {args.prefix}.@0\n')
 
 
 def _main():
diff --git a/tools/gn2bp/Android.bp.swp b/tools/gn2bp/Android.bp.swp
deleted file mode 100644
index 212735a..0000000
--- a/tools/gn2bp/Android.bp.swp
+++ /dev/null
@@ -1,26467 +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.
-//
-// This file is automatically generated by gen_android_bp. Do not edit.
-
-// GN: //base/allocator:buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_allocator_buildflags__android_arm",
-    cmd: "echo '--flags USE_PARTITION_ALLOC=\"false\" USE_ALLOCATOR_SHIM=\"true\" USE_PARTITION_ALLOC_AS_MALLOC=\"false\" USE_BACKUP_REF_PTR=\"false\" USE_ASAN_BACKUP_REF_PTR=\"false\" USE_PARTITION_ALLOC_AS_GWP_ASAN_STORE=\"false\" USE_MTE_CHECKED_PTR=\"false\" FORCE_ENABLE_RAW_PTR_EXCLUSION=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator:buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_allocator_buildflags__android_arm64",
-    cmd: "echo '--flags USE_PARTITION_ALLOC=\"false\" USE_ALLOCATOR_SHIM=\"true\" USE_PARTITION_ALLOC_AS_MALLOC=\"false\" USE_BACKUP_REF_PTR=\"false\" USE_ASAN_BACKUP_REF_PTR=\"false\" USE_PARTITION_ALLOC_AS_GWP_ASAN_STORE=\"false\" USE_MTE_CHECKED_PTR=\"false\" FORCE_ENABLE_RAW_PTR_EXCLUSION=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator:buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_allocator_buildflags__android_x86",
-    cmd: "echo '--flags USE_PARTITION_ALLOC=\"false\" USE_ALLOCATOR_SHIM=\"true\" USE_PARTITION_ALLOC_AS_MALLOC=\"false\" USE_BACKUP_REF_PTR=\"false\" USE_ASAN_BACKUP_REF_PTR=\"false\" USE_PARTITION_ALLOC_AS_GWP_ASAN_STORE=\"false\" USE_MTE_CHECKED_PTR=\"false\" FORCE_ENABLE_RAW_PTR_EXCLUSION=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator:buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_allocator_buildflags__android_x86_64",
-    cmd: "echo '--flags USE_PARTITION_ALLOC=\"false\" USE_ALLOCATOR_SHIM=\"true\" USE_PARTITION_ALLOC_AS_MALLOC=\"false\" USE_BACKUP_REF_PTR=\"false\" USE_ASAN_BACKUP_REF_PTR=\"false\" USE_PARTITION_ALLOC_AS_GWP_ASAN_STORE=\"false\" USE_MTE_CHECKED_PTR=\"false\" FORCE_ENABLE_RAW_PTR_EXCLUSION=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator:buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_allocator_buildflags__host",
-    cmd: "echo '--flags USE_PARTITION_ALLOC=\"false\" USE_ALLOCATOR_SHIM=\"true\" USE_PARTITION_ALLOC_AS_MALLOC=\"false\" USE_BACKUP_REF_PTR=\"false\" USE_ASAN_BACKUP_REF_PTR=\"false\" USE_PARTITION_ALLOC_AS_GWP_ASAN_STORE=\"false\" USE_MTE_CHECKED_PTR=\"false\" FORCE_ENABLE_RAW_PTR_EXCLUSION=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/allocator/buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:chromecast_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_chromecast_buildflags__android_arm",
-    cmd: "echo '--flags PA_IS_CAST_ANDROID=\"false\" PA_IS_CASTOS=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:chromecast_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/chromecast_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:chromecast_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_chromecast_buildflags__android_arm64",
-    cmd: "echo '--flags PA_IS_CAST_ANDROID=\"false\" PA_IS_CASTOS=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:chromecast_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/chromecast_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:chromecast_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_chromecast_buildflags__android_x86",
-    cmd: "echo '--flags PA_IS_CAST_ANDROID=\"false\" PA_IS_CASTOS=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:chromecast_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/chromecast_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:chromecast_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_chromecast_buildflags__android_x86_64",
-    cmd: "echo '--flags PA_IS_CAST_ANDROID=\"false\" PA_IS_CASTOS=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:chromecast_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/chromecast_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:chromecast_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_chromecast_buildflags__host",
-    cmd: "echo '--flags PA_IS_CAST_ANDROID=\"false\" PA_IS_CASTOS=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:chromecast_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/allocator/partition_allocator/chromecast_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:chromeos_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_chromeos_buildflags__android_arm",
-    cmd: "echo '--flags PA_IS_CHROMEOS_ASH=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:chromeos_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/chromeos_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:chromeos_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_chromeos_buildflags__android_arm64",
-    cmd: "echo '--flags PA_IS_CHROMEOS_ASH=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:chromeos_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/chromeos_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:chromeos_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_chromeos_buildflags__android_x86",
-    cmd: "echo '--flags PA_IS_CHROMEOS_ASH=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:chromeos_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/chromeos_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:chromeos_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_chromeos_buildflags__android_x86_64",
-    cmd: "echo '--flags PA_IS_CHROMEOS_ASH=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:chromeos_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/chromeos_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:chromeos_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_chromeos_buildflags__host",
-    cmd: "echo '--flags PA_IS_CHROMEOS_ASH=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:chromeos_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/allocator/partition_allocator/chromeos_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:debugging_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_debugging_buildflags__android_arm",
-    cmd: "echo '--flags PA_DCHECK_IS_ON=\"true\" PA_EXPENSIVE_DCHECKS_ARE_ON=\"true\" PA_DCHECK_IS_CONFIGURABLE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:debugging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/partition_alloc_base/debug/debugging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:debugging_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_debugging_buildflags__android_arm64",
-    cmd: "echo '--flags PA_DCHECK_IS_ON=\"true\" PA_EXPENSIVE_DCHECKS_ARE_ON=\"true\" PA_DCHECK_IS_CONFIGURABLE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:debugging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/partition_alloc_base/debug/debugging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:debugging_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_debugging_buildflags__android_x86",
-    cmd: "echo '--flags PA_DCHECK_IS_ON=\"true\" PA_EXPENSIVE_DCHECKS_ARE_ON=\"true\" PA_DCHECK_IS_CONFIGURABLE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:debugging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/partition_alloc_base/debug/debugging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:debugging_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_debugging_buildflags__android_x86_64",
-    cmd: "echo '--flags PA_DCHECK_IS_ON=\"true\" PA_EXPENSIVE_DCHECKS_ARE_ON=\"true\" PA_DCHECK_IS_CONFIGURABLE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:debugging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/partition_alloc_base/debug/debugging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:debugging_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_debugging_buildflags__host",
-    cmd: "echo '--flags PA_DCHECK_IS_ON=\"true\" PA_EXPENSIVE_DCHECKS_ARE_ON=\"true\" PA_DCHECK_IS_CONFIGURABLE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:debugging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/allocator/partition_allocator/partition_alloc_base/debug/debugging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:logging_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_logging_buildflags__android_arm",
-    cmd: "echo '--flags PA_ENABLE_LOG_ERROR_NOT_REACHED=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:logging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/logging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:logging_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_logging_buildflags__android_arm64",
-    cmd: "echo '--flags PA_ENABLE_LOG_ERROR_NOT_REACHED=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:logging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/logging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:logging_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_logging_buildflags__android_x86",
-    cmd: "echo '--flags PA_ENABLE_LOG_ERROR_NOT_REACHED=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:logging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/logging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:logging_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_logging_buildflags__android_x86_64",
-    cmd: "echo '--flags PA_ENABLE_LOG_ERROR_NOT_REACHED=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:logging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/logging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:logging_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_logging_buildflags__host",
-    cmd: "echo '--flags PA_ENABLE_LOG_ERROR_NOT_REACHED=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:logging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/allocator/partition_allocator/logging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:partition_alloc
-cc_library_static {
-    name: "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-    srcs: [
-        "base/allocator/partition_allocator/address_pool_manager.cc",
-        "base/allocator/partition_allocator/address_pool_manager_bitmap.cc",
-        "base/allocator/partition_allocator/address_space_randomization.cc",
-        "base/allocator/partition_allocator/allocation_guard.cc",
-        "base/allocator/partition_allocator/dangling_raw_ptr_checks.cc",
-        "base/allocator/partition_allocator/gwp_asan_support.cc",
-        "base/allocator/partition_allocator/memory_reclaimer.cc",
-        "base/allocator/partition_allocator/oom.cc",
-        "base/allocator/partition_allocator/oom_callback.cc",
-        "base/allocator/partition_allocator/page_allocator.cc",
-        "base/allocator/partition_allocator/page_allocator_internals_posix.cc",
-        "base/allocator/partition_allocator/partition_address_space.cc",
-        "base/allocator/partition_allocator/partition_alloc.cc",
-        "base/allocator/partition_allocator/partition_alloc_base/check.cc",
-        "base/allocator/partition_allocator/partition_alloc_base/cpu.cc",
-        "base/allocator/partition_allocator/partition_alloc_base/debug/alias.cc",
-        "base/allocator/partition_allocator/partition_alloc_base/files/file_util_posix.cc",
-        "base/allocator/partition_allocator/partition_alloc_base/logging.cc",
-        "base/allocator/partition_allocator/partition_alloc_base/memory/ref_counted.cc",
-        "base/allocator/partition_allocator/partition_alloc_base/pkey.cc",
-        "base/allocator/partition_allocator/partition_alloc_base/posix/safe_strerror.cc",
-        "base/allocator/partition_allocator/partition_alloc_base/rand_util.cc",
-        "base/allocator/partition_allocator/partition_alloc_base/rand_util_posix.cc",
-        "base/allocator/partition_allocator/partition_alloc_base/strings/stringprintf.cc",
-        "base/allocator/partition_allocator/partition_alloc_base/threading/platform_thread.cc",
-        "base/allocator/partition_allocator/partition_alloc_base/threading/platform_thread_posix.cc",
-        "base/allocator/partition_allocator/partition_alloc_base/time/time.cc",
-        "base/allocator/partition_allocator/partition_alloc_base/time/time_conversion_posix.cc",
-        "base/allocator/partition_allocator/partition_alloc_base/time/time_now_posix.cc",
-        "base/allocator/partition_allocator/partition_alloc_base/time/time_override.cc",
-        "base/allocator/partition_allocator/partition_alloc_hooks.cc",
-        "base/allocator/partition_allocator/partition_bucket.cc",
-        "base/allocator/partition_allocator/partition_oom.cc",
-        "base/allocator/partition_allocator/partition_page.cc",
-        "base/allocator/partition_allocator/partition_root.cc",
-        "base/allocator/partition_allocator/partition_stats.cc",
-        "base/allocator/partition_allocator/random.cc",
-        "base/allocator/partition_allocator/reservation_offset_table.cc",
-        "base/allocator/partition_allocator/spinning_mutex.cc",
-        "base/allocator/partition_allocator/starscan/metadata_allocator.cc",
-        "base/allocator/partition_allocator/starscan/pcscan.cc",
-        "base/allocator/partition_allocator/starscan/pcscan_internal.cc",
-        "base/allocator/partition_allocator/starscan/pcscan_scheduling.cc",
-        "base/allocator/partition_allocator/starscan/snapshot.cc",
-        "base/allocator/partition_allocator/starscan/stack/stack.cc",
-        "base/allocator/partition_allocator/starscan/stats_collector.cc",
-        "base/allocator/partition_allocator/starscan/write_protector.cc",
-        "base/allocator/partition_allocator/tagging.cc",
-        "base/allocator/partition_allocator/thread_cache.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DIS_PARTITION_ALLOC_IMPL",
-        "-DPA_PCSCAN_STACK_SUPPORTED",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            srcs: [
-                ":cronet_aml_third_party_android_ndk_cpu_features",
-                "base/allocator/partition_allocator/partition_alloc_base/files/file_path.cc",
-                "base/allocator/partition_allocator/partition_alloc_base/native_library.cc",
-                "base/allocator/partition_allocator/partition_alloc_base/native_library_posix.cc",
-                "base/allocator/partition_allocator/partition_alloc_base/time/time_android.cc",
-                "base/allocator/partition_allocator/starscan/stack/asm/arm/push_registers_asm.cc",
-            ],
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            local_include_dirs: [
-                "third_party/android_ndk/sources/android/cpufeatures/",
-            ],
-            generated_headers: [
-                "cronet_aml_base_allocator_partition_allocator_chromecast_buildflags__android_arm",
-                "cronet_aml_base_allocator_partition_allocator_chromeos_buildflags__android_arm",
-                "cronet_aml_base_allocator_partition_allocator_debugging_buildflags__android_arm",
-                "cronet_aml_base_allocator_partition_allocator_logging_buildflags__android_arm",
-                "cronet_aml_base_allocator_partition_allocator_partition_alloc_buildflags__android_arm",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_allocator_partition_allocator_chromecast_buildflags__android_arm",
-                "cronet_aml_base_allocator_partition_allocator_chromeos_buildflags__android_arm",
-                "cronet_aml_base_allocator_partition_allocator_debugging_buildflags__android_arm",
-                "cronet_aml_base_allocator_partition_allocator_logging_buildflags__android_arm",
-                "cronet_aml_base_allocator_partition_allocator_partition_alloc_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            srcs: [
-                ":cronet_aml_third_party_android_ndk_cpu_features",
-                "base/allocator/partition_allocator/partition_alloc_base/files/file_path.cc",
-                "base/allocator/partition_allocator/partition_alloc_base/native_library.cc",
-                "base/allocator/partition_allocator/partition_alloc_base/native_library_posix.cc",
-                "base/allocator/partition_allocator/partition_alloc_base/time/time_android.cc",
-                "base/allocator/partition_allocator/starscan/stack/asm/arm64/push_registers_asm.cc",
-            ],
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-march=armv8-a+memtag",
-            ],
-            local_include_dirs: [
-                "third_party/android_ndk/sources/android/cpufeatures/",
-            ],
-            generated_headers: [
-                "cronet_aml_base_allocator_partition_allocator_chromecast_buildflags__android_arm64",
-                "cronet_aml_base_allocator_partition_allocator_chromeos_buildflags__android_arm64",
-                "cronet_aml_base_allocator_partition_allocator_debugging_buildflags__android_arm64",
-                "cronet_aml_base_allocator_partition_allocator_logging_buildflags__android_arm64",
-                "cronet_aml_base_allocator_partition_allocator_partition_alloc_buildflags__android_arm64",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_allocator_partition_allocator_chromecast_buildflags__android_arm64",
-                "cronet_aml_base_allocator_partition_allocator_chromeos_buildflags__android_arm64",
-                "cronet_aml_base_allocator_partition_allocator_debugging_buildflags__android_arm64",
-                "cronet_aml_base_allocator_partition_allocator_logging_buildflags__android_arm64",
-                "cronet_aml_base_allocator_partition_allocator_partition_alloc_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            srcs: [
-                ":cronet_aml_third_party_android_ndk_cpu_features",
-                "base/allocator/partition_allocator/partition_alloc_base/files/file_path.cc",
-                "base/allocator/partition_allocator/partition_alloc_base/native_library.cc",
-                "base/allocator/partition_allocator/partition_alloc_base/native_library_posix.cc",
-                "base/allocator/partition_allocator/partition_alloc_base/time/time_android.cc",
-                "base/allocator/partition_allocator/starscan/stack/asm/x86/push_registers_asm.cc",
-            ],
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            local_include_dirs: [
-                "third_party/android_ndk/sources/android/cpufeatures/",
-            ],
-            generated_headers: [
-                "cronet_aml_base_allocator_partition_allocator_chromecast_buildflags__android_x86",
-                "cronet_aml_base_allocator_partition_allocator_chromeos_buildflags__android_x86",
-                "cronet_aml_base_allocator_partition_allocator_debugging_buildflags__android_x86",
-                "cronet_aml_base_allocator_partition_allocator_logging_buildflags__android_x86",
-                "cronet_aml_base_allocator_partition_allocator_partition_alloc_buildflags__android_x86",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_allocator_partition_allocator_chromecast_buildflags__android_x86",
-                "cronet_aml_base_allocator_partition_allocator_chromeos_buildflags__android_x86",
-                "cronet_aml_base_allocator_partition_allocator_debugging_buildflags__android_x86",
-                "cronet_aml_base_allocator_partition_allocator_logging_buildflags__android_x86",
-                "cronet_aml_base_allocator_partition_allocator_partition_alloc_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            srcs: [
-                ":cronet_aml_third_party_android_ndk_cpu_features",
-                "base/allocator/partition_allocator/partition_alloc_base/files/file_path.cc",
-                "base/allocator/partition_allocator/partition_alloc_base/native_library.cc",
-                "base/allocator/partition_allocator/partition_alloc_base/native_library_posix.cc",
-                "base/allocator/partition_allocator/partition_alloc_base/time/time_android.cc",
-                "base/allocator/partition_allocator/starscan/stack/asm/x64/push_registers_asm.cc",
-            ],
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            local_include_dirs: [
-                "third_party/android_ndk/sources/android/cpufeatures/",
-            ],
-            generated_headers: [
-                "cronet_aml_base_allocator_partition_allocator_chromecast_buildflags__android_x86_64",
-                "cronet_aml_base_allocator_partition_allocator_chromeos_buildflags__android_x86_64",
-                "cronet_aml_base_allocator_partition_allocator_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_allocator_partition_allocator_logging_buildflags__android_x86_64",
-                "cronet_aml_base_allocator_partition_allocator_partition_alloc_buildflags__android_x86_64",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_allocator_partition_allocator_chromecast_buildflags__android_x86_64",
-                "cronet_aml_base_allocator_partition_allocator_chromeos_buildflags__android_x86_64",
-                "cronet_aml_base_allocator_partition_allocator_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_allocator_partition_allocator_logging_buildflags__android_x86_64",
-                "cronet_aml_base_allocator_partition_allocator_partition_alloc_buildflags__android_x86_64",
-            ],
-        },
-        host: {
-            srcs: [
-                "base/allocator/partition_allocator/starscan/stack/asm/x64/push_registers_asm.cc",
-            ],
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_allocator_partition_allocator_chromecast_buildflags__host",
-                "cronet_aml_base_allocator_partition_allocator_chromeos_buildflags__host",
-                "cronet_aml_base_allocator_partition_allocator_debugging_buildflags__host",
-                "cronet_aml_base_allocator_partition_allocator_logging_buildflags__host",
-                "cronet_aml_base_allocator_partition_allocator_partition_alloc_buildflags__host",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_allocator_partition_allocator_chromecast_buildflags__host",
-                "cronet_aml_base_allocator_partition_allocator_chromeos_buildflags__host",
-                "cronet_aml_base_allocator_partition_allocator_debugging_buildflags__host",
-                "cronet_aml_base_allocator_partition_allocator_logging_buildflags__host",
-                "cronet_aml_base_allocator_partition_allocator_partition_alloc_buildflags__host",
-            ],
-        },
-    },
-}
-
-// GN: //base/allocator/partition_allocator:partition_alloc_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_partition_alloc_buildflags__android_arm",
-    cmd: "echo '--flags ENABLE_PARTITION_ALLOC_AS_MALLOC_SUPPORT=\"true\" ENABLE_BACKUP_REF_PTR_SUPPORT=\"true\" ENABLE_BACKUP_REF_PTR_SLOW_CHECKS=\"false\" ENABLE_DANGLING_RAW_PTR_CHECKS=\"false\" PUT_REF_COUNT_IN_PREVIOUS_SLOT=\"true\" ENABLE_GWP_ASAN_SUPPORT=\"true\" ENABLE_MTE_CHECKED_PTR_SUPPORT=\"false\" RECORD_ALLOC_INFO=\"false\" USE_FREESLOT_BITMAP=\"false\" GLUE_CORE_POOLS=\"false\" ENABLE_SHADOW_METADATA_FOR_64_BITS_POINTERS=\"false\" STARSCAN=\"true\" PA_USE_BASE_TRACING=\"true\" ENABLE_PKEYS=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:partition_alloc_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/partition_alloc_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:partition_alloc_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_partition_alloc_buildflags__android_arm64",
-    cmd: "echo '--flags ENABLE_PARTITION_ALLOC_AS_MALLOC_SUPPORT=\"true\" ENABLE_BACKUP_REF_PTR_SUPPORT=\"true\" ENABLE_BACKUP_REF_PTR_SLOW_CHECKS=\"false\" ENABLE_DANGLING_RAW_PTR_CHECKS=\"false\" PUT_REF_COUNT_IN_PREVIOUS_SLOT=\"true\" ENABLE_GWP_ASAN_SUPPORT=\"true\" ENABLE_MTE_CHECKED_PTR_SUPPORT=\"false\" RECORD_ALLOC_INFO=\"false\" USE_FREESLOT_BITMAP=\"false\" GLUE_CORE_POOLS=\"false\" ENABLE_SHADOW_METADATA_FOR_64_BITS_POINTERS=\"false\" STARSCAN=\"true\" PA_USE_BASE_TRACING=\"true\" ENABLE_PKEYS=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:partition_alloc_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/partition_alloc_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:partition_alloc_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_partition_alloc_buildflags__android_x86",
-    cmd: "echo '--flags ENABLE_PARTITION_ALLOC_AS_MALLOC_SUPPORT=\"true\" ENABLE_BACKUP_REF_PTR_SUPPORT=\"true\" ENABLE_BACKUP_REF_PTR_SLOW_CHECKS=\"false\" ENABLE_DANGLING_RAW_PTR_CHECKS=\"false\" PUT_REF_COUNT_IN_PREVIOUS_SLOT=\"true\" ENABLE_GWP_ASAN_SUPPORT=\"true\" ENABLE_MTE_CHECKED_PTR_SUPPORT=\"false\" RECORD_ALLOC_INFO=\"false\" USE_FREESLOT_BITMAP=\"false\" GLUE_CORE_POOLS=\"false\" ENABLE_SHADOW_METADATA_FOR_64_BITS_POINTERS=\"false\" STARSCAN=\"true\" PA_USE_BASE_TRACING=\"true\" ENABLE_PKEYS=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:partition_alloc_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/partition_alloc_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:partition_alloc_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_partition_alloc_buildflags__android_x86_64",
-    cmd: "echo '--flags ENABLE_PARTITION_ALLOC_AS_MALLOC_SUPPORT=\"true\" ENABLE_BACKUP_REF_PTR_SUPPORT=\"true\" ENABLE_BACKUP_REF_PTR_SLOW_CHECKS=\"false\" ENABLE_DANGLING_RAW_PTR_CHECKS=\"false\" PUT_REF_COUNT_IN_PREVIOUS_SLOT=\"true\" ENABLE_GWP_ASAN_SUPPORT=\"true\" ENABLE_MTE_CHECKED_PTR_SUPPORT=\"false\" RECORD_ALLOC_INFO=\"false\" USE_FREESLOT_BITMAP=\"false\" GLUE_CORE_POOLS=\"false\" ENABLE_SHADOW_METADATA_FOR_64_BITS_POINTERS=\"false\" STARSCAN=\"true\" PA_USE_BASE_TRACING=\"true\" ENABLE_PKEYS=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:partition_alloc_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/allocator/partition_allocator/partition_alloc_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/allocator/partition_allocator:partition_alloc_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_allocator_partition_allocator_partition_alloc_buildflags__host",
-    cmd: "echo '--flags ENABLE_PARTITION_ALLOC_AS_MALLOC_SUPPORT=\"true\" ENABLE_BACKUP_REF_PTR_SUPPORT=\"true\" ENABLE_BACKUP_REF_PTR_SLOW_CHECKS=\"false\" ENABLE_DANGLING_RAW_PTR_CHECKS=\"false\" PUT_REF_COUNT_IN_PREVIOUS_SLOT=\"true\" ENABLE_GWP_ASAN_SUPPORT=\"true\" ENABLE_MTE_CHECKED_PTR_SUPPORT=\"false\" RECORD_ALLOC_INFO=\"false\" USE_FREESLOT_BITMAP=\"false\" GLUE_CORE_POOLS=\"false\" ENABLE_SHADOW_METADATA_FOR_64_BITS_POINTERS=\"false\" STARSCAN=\"true\" PA_USE_BASE_TRACING=\"true\" ENABLE_PKEYS=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base/allocator/partition_allocator:partition_alloc_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/allocator/partition_allocator/partition_alloc_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:anchor_functions_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_anchor_functions_buildflags__android_arm",
-    cmd: "echo '--flags USE_LLD=\"true\" SUPPORTS_CODE_ORDERING=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:anchor_functions_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/android/library_loader/anchor_functions_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:anchor_functions_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_anchor_functions_buildflags__android_arm64",
-    cmd: "echo '--flags USE_LLD=\"true\" SUPPORTS_CODE_ORDERING=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:anchor_functions_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/android/library_loader/anchor_functions_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:anchor_functions_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_anchor_functions_buildflags__android_x86",
-    cmd: "echo '--flags USE_LLD=\"true\" SUPPORTS_CODE_ORDERING=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:anchor_functions_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/android/library_loader/anchor_functions_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:anchor_functions_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_anchor_functions_buildflags__android_x86_64",
-    cmd: "echo '--flags USE_LLD=\"true\" SUPPORTS_CODE_ORDERING=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:anchor_functions_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/android/library_loader/anchor_functions_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:anchor_functions_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_anchor_functions_buildflags__host",
-    cmd: "echo '--flags USE_LLD=\"true\" SUPPORTS_CODE_ORDERING=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:anchor_functions_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/android/library_loader/anchor_functions_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:android_runtime_jni_headers__android_arm
-cc_genrule {
-    name: "cronet_aml_base_android_runtime_jni_headers__android_arm",
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/base/android_runtime_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--jar_file " +
-         "$(location third_party/android_sdk/public/platforms/android-33/android.jar) " +
-         "--output_name " +
-         "Runnable_jni.h " +
-         "--output_name " +
-         "Runtime_jni.h " +
-         "--input_file " +
-         "java/lang/Runnable.class " +
-         "--input_file " +
-         "java/lang/Runtime.class " +
-         "--javap " +
-         "$$(find out/.path -name javap)",
-    out: [
-        "base/android_runtime_jni_headers/Runnable_jni.h",
-        "base/android_runtime_jni_headers/Runtime_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-        "third_party/android_sdk/public/platforms/android-33/android.jar",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:android_runtime_jni_headers__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_android_runtime_jni_headers__android_arm64",
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/base/android_runtime_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--jar_file " +
-         "$(location third_party/android_sdk/public/platforms/android-33/android.jar) " +
-         "--output_name " +
-         "Runnable_jni.h " +
-         "--output_name " +
-         "Runtime_jni.h " +
-         "--input_file " +
-         "java/lang/Runnable.class " +
-         "--input_file " +
-         "java/lang/Runtime.class " +
-         "--javap " +
-         "$$(find out/.path -name javap)",
-    out: [
-        "base/android_runtime_jni_headers/Runnable_jni.h",
-        "base/android_runtime_jni_headers/Runtime_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-        "third_party/android_sdk/public/platforms/android-33/android.jar",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:android_runtime_jni_headers__android_x86
-cc_genrule {
-    name: "cronet_aml_base_android_runtime_jni_headers__android_x86",
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/base/android_runtime_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--jar_file " +
-         "$(location third_party/android_sdk/public/platforms/android-33/android.jar) " +
-         "--output_name " +
-         "Runnable_jni.h " +
-         "--output_name " +
-         "Runtime_jni.h " +
-         "--input_file " +
-         "java/lang/Runnable.class " +
-         "--input_file " +
-         "java/lang/Runtime.class " +
-         "--javap " +
-         "$$(find out/.path -name javap)",
-    out: [
-        "base/android_runtime_jni_headers/Runnable_jni.h",
-        "base/android_runtime_jni_headers/Runtime_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-        "third_party/android_sdk/public/platforms/android-33/android.jar",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:android_runtime_jni_headers__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_android_runtime_jni_headers__android_x86_64",
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/base/android_runtime_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--jar_file " +
-         "$(location third_party/android_sdk/public/platforms/android-33/android.jar) " +
-         "--output_name " +
-         "Runnable_jni.h " +
-         "--output_name " +
-         "Runtime_jni.h " +
-         "--input_file " +
-         "java/lang/Runnable.class " +
-         "--input_file " +
-         "java/lang/Runtime.class " +
-         "--javap " +
-         "$$(find out/.path -name javap)",
-    out: [
-        "base/android_runtime_jni_headers/Runnable_jni.h",
-        "base/android_runtime_jni_headers/Runtime_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-        "third_party/android_sdk/public/platforms/android-33/android.jar",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:base
-cc_library_static {
-    name: "cronet_aml_base_base",
-    srcs: [
-        ":cronet_aml_third_party_abseil_cpp_absl_base_base",
-        ":cronet_aml_third_party_abseil_cpp_absl_base_log_severity",
-        ":cronet_aml_third_party_abseil_cpp_absl_base_malloc_internal",
-        ":cronet_aml_third_party_abseil_cpp_absl_base_raw_logging_internal",
-        ":cronet_aml_third_party_abseil_cpp_absl_base_spinlock_wait",
-        ":cronet_aml_third_party_abseil_cpp_absl_base_strerror",
-        ":cronet_aml_third_party_abseil_cpp_absl_base_throw_delegate",
-        ":cronet_aml_third_party_abseil_cpp_absl_container_hashtablez_sampler",
-        ":cronet_aml_third_party_abseil_cpp_absl_container_raw_hash_set",
-        ":cronet_aml_third_party_abseil_cpp_absl_debugging_debugging_internal",
-        ":cronet_aml_third_party_abseil_cpp_absl_debugging_demangle_internal",
-        ":cronet_aml_third_party_abseil_cpp_absl_debugging_examine_stack",
-        ":cronet_aml_third_party_abseil_cpp_absl_debugging_failure_signal_handler",
-        ":cronet_aml_third_party_abseil_cpp_absl_debugging_stacktrace",
-        ":cronet_aml_third_party_abseil_cpp_absl_debugging_symbolize",
-        ":cronet_aml_third_party_abseil_cpp_absl_hash_city",
-        ":cronet_aml_third_party_abseil_cpp_absl_hash_hash",
-        ":cronet_aml_third_party_abseil_cpp_absl_hash_low_level_hash",
-        ":cronet_aml_third_party_abseil_cpp_absl_numeric_int128",
-        ":cronet_aml_third_party_abseil_cpp_absl_profiling_exponential_biased",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_distributions",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_platform",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_pool_urbg",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_randen",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_randen_hwaes",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_randen_hwaes_impl",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_randen_slow",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_seed_material",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_seed_gen_exception",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_seed_sequences",
-        ":cronet_aml_third_party_abseil_cpp_absl_status_status",
-        ":cronet_aml_third_party_abseil_cpp_absl_status_statusor",
-        ":cronet_aml_third_party_abseil_cpp_absl_strings_cord",
-        ":cronet_aml_third_party_abseil_cpp_absl_strings_cord_internal",
-        ":cronet_aml_third_party_abseil_cpp_absl_strings_cordz_functions",
-        ":cronet_aml_third_party_abseil_cpp_absl_strings_cordz_handle",
-        ":cronet_aml_third_party_abseil_cpp_absl_strings_cordz_info",
-        ":cronet_aml_third_party_abseil_cpp_absl_strings_internal",
-        ":cronet_aml_third_party_abseil_cpp_absl_strings_str_format_internal",
-        ":cronet_aml_third_party_abseil_cpp_absl_strings_strings",
-        ":cronet_aml_third_party_abseil_cpp_absl_synchronization_graphcycles_internal",
-        ":cronet_aml_third_party_abseil_cpp_absl_synchronization_synchronization",
-        ":cronet_aml_third_party_abseil_cpp_absl_time_internal_cctz_civil_time",
-        ":cronet_aml_third_party_abseil_cpp_absl_time_internal_cctz_time_zone",
-        ":cronet_aml_third_party_abseil_cpp_absl_time_time",
-        ":cronet_aml_third_party_abseil_cpp_absl_types_bad_optional_access",
-        ":cronet_aml_third_party_abseil_cpp_absl_types_bad_variant_access",
-        "base/allocator/allocator_check.cc",
-        "base/allocator/allocator_extension.cc",
-        "base/allocator/dispatcher/dispatcher.cc",
-        "base/allocator/dispatcher/internal/dispatch_data.cc",
-        "base/allocator/dispatcher/reentry_guard.cc",
-        "base/allocator/partition_allocator/shim/allocator_shim.cc",
-        "base/at_exit.cc",
-        "base/barrier_closure.cc",
-        "base/base64.cc",
-        "base/base64url.cc",
-        "base/base_paths.cc",
-        "base/big_endian.cc",
-        "base/build_time.cc",
-        "base/callback_list.cc",
-        "base/check.cc",
-        "base/check_is_test.cc",
-        "base/check_op.cc",
-        "base/command_line.cc",
-        "base/containers/flat_tree.cc",
-        "base/containers/intrusive_heap.cc",
-        "base/containers/linked_list.cc",
-        "base/cpu.cc",
-        "base/cpu_reduction_experiment.cc",
-        "base/debug/activity_analyzer.cc",
-        "base/debug/activity_tracker.cc",
-        "base/debug/alias.cc",
-        "base/debug/asan_invalid_access.cc",
-        "base/debug/buffered_dwarf_reader.cc",
-        "base/debug/crash_logging.cc",
-        "base/debug/debugger.cc",
-        "base/debug/debugger_posix.cc",
-        "base/debug/dump_without_crashing.cc",
-        "base/debug/dwarf_line_no.cc",
-        "base/debug/elf_reader.cc",
-        "base/debug/proc_maps_linux.cc",
-        "base/debug/profiler.cc",
-        "base/debug/stack_trace.cc",
-        "base/debug/task_trace.cc",
-        "base/environment.cc",
-        "base/feature_list.cc",
-        "base/features.cc",
-        "base/file_descriptor_posix.cc",
-        "base/file_descriptor_store.cc",
-        "base/files/file.cc",
-        "base/files/file_descriptor_watcher_posix.cc",
-        "base/files/file_enumerator.cc",
-        "base/files/file_enumerator_posix.cc",
-        "base/files/file_path.cc",
-        "base/files/file_path_watcher.cc",
-        "base/files/file_path_watcher_inotify.cc",
-        "base/files/file_posix.cc",
-        "base/files/file_proxy.cc",
-        "base/files/file_tracing.cc",
-        "base/files/file_util.cc",
-        "base/files/file_util_posix.cc",
-        "base/files/important_file_writer.cc",
-        "base/files/important_file_writer_cleaner.cc",
-        "base/files/memory_mapped_file.cc",
-        "base/files/memory_mapped_file_posix.cc",
-        "base/files/safe_base_name.cc",
-        "base/files/scoped_file.cc",
-        "base/files/scoped_temp_dir.cc",
-        "base/functional/callback_helpers.cc",
-        "base/functional/callback_internal.cc",
-        "base/guid.cc",
-        "base/hash/hash.cc",
-        "base/hash/legacy_hash.cc",
-        "base/hash/md5_boringssl.cc",
-        "base/hash/sha1_boringssl.cc",
-        "base/json/json_file_value_serializer.cc",
-        "base/json/json_parser.cc",
-        "base/json/json_reader.cc",
-        "base/json/json_string_value_serializer.cc",
-        "base/json/json_value_converter.cc",
-        "base/json/json_writer.cc",
-        "base/json/string_escape.cc",
-        "base/json/values_util.cc",
-        "base/lazy_instance_helpers.cc",
-        "base/linux_util.cc",
-        "base/location.cc",
-        "base/logging.cc",
-        "base/memory/aligned_memory.cc",
-        "base/memory/discardable_memory.cc",
-        "base/memory/discardable_memory_allocator.cc",
-        "base/memory/discardable_shared_memory.cc",
-        "base/memory/madv_free_discardable_memory_allocator_posix.cc",
-        "base/memory/madv_free_discardable_memory_posix.cc",
-        "base/memory/memory_pressure_listener.cc",
-        "base/memory/memory_pressure_monitor.cc",
-        "base/memory/nonscannable_memory.cc",
-        "base/memory/page_size_posix.cc",
-        "base/memory/platform_shared_memory_handle.cc",
-        "base/memory/platform_shared_memory_region.cc",
-        "base/memory/raw_ptr.cc",
-        "base/memory/raw_ptr_asan_bound_arg_tracker.cc",
-        "base/memory/raw_ptr_asan_service.cc",
-        "base/memory/read_only_shared_memory_region.cc",
-        "base/memory/ref_counted.cc",
-        "base/memory/ref_counted_memory.cc",
-        "base/memory/shared_memory_mapper.cc",
-        "base/memory/shared_memory_mapping.cc",
-        "base/memory/shared_memory_security_policy.cc",
-        "base/memory/shared_memory_tracker.cc",
-        "base/memory/unsafe_shared_memory_pool.cc",
-        "base/memory/unsafe_shared_memory_region.cc",
-        "base/memory/weak_ptr.cc",
-        "base/memory/writable_shared_memory_region.cc",
-        "base/message_loop/message_pump.cc",
-        "base/message_loop/message_pump_default.cc",
-        "base/message_loop/message_pump_epoll.cc",
-        "base/message_loop/message_pump_libevent.cc",
-        "base/message_loop/watchable_io_message_pump_posix.cc",
-        "base/message_loop/work_id_provider.cc",
-        "base/metrics/bucket_ranges.cc",
-        "base/metrics/crc32.cc",
-        "base/metrics/dummy_histogram.cc",
-        "base/metrics/field_trial.cc",
-        "base/metrics/field_trial_param_associator.cc",
-        "base/metrics/field_trial_params.cc",
-        "base/metrics/histogram.cc",
-        "base/metrics/histogram_base.cc",
-        "base/metrics/histogram_delta_serialization.cc",
-        "base/metrics/histogram_functions.cc",
-        "base/metrics/histogram_samples.cc",
-        "base/metrics/histogram_snapshot_manager.cc",
-        "base/metrics/metrics_hashes.cc",
-        "base/metrics/persistent_histogram_allocator.cc",
-        "base/metrics/persistent_histogram_storage.cc",
-        "base/metrics/persistent_memory_allocator.cc",
-        "base/metrics/persistent_sample_map.cc",
-        "base/metrics/ranges_manager.cc",
-        "base/metrics/sample_map.cc",
-        "base/metrics/sample_vector.cc",
-        "base/metrics/single_sample_metrics.cc",
-        "base/metrics/sparse_histogram.cc",
-        "base/metrics/statistics_recorder.cc",
-        "base/metrics/user_metrics.cc",
-        "base/native_library.cc",
-        "base/native_library_posix.cc",
-        "base/observer_list_internal.cc",
-        "base/observer_list_threadsafe.cc",
-        "base/observer_list_types.cc",
-        "base/one_shot_event.cc",
-        "base/path_service.cc",
-        "base/pending_task.cc",
-        "base/pickle.cc",
-        "base/posix/can_lower_nice_to.cc",
-        "base/posix/file_descriptor_shuffle.cc",
-        "base/posix/global_descriptors.cc",
-        "base/posix/safe_strerror.cc",
-        "base/posix/unix_domain_socket.cc",
-        "base/power_monitor/battery_level_provider.cc",
-        "base/power_monitor/battery_state_sampler.cc",
-        "base/power_monitor/moving_average.cc",
-        "base/power_monitor/power_monitor.cc",
-        "base/power_monitor/power_monitor_device_source.cc",
-        "base/power_monitor/power_monitor_features.cc",
-        "base/power_monitor/power_monitor_source.cc",
-        "base/power_monitor/sampling_event_source.cc",
-        "base/power_monitor/timer_sampling_event_source.cc",
-        "base/process/environment_internal.cc",
-        "base/process/internal_linux.cc",
-        "base/process/kill.cc",
-        "base/process/kill_posix.cc",
-        "base/process/launch.cc",
-        "base/process/launch_posix.cc",
-        "base/process/memory.cc",
-        "base/process/memory_linux.cc",
-        "base/process/process_handle.cc",
-        "base/process/process_handle_linux.cc",
-        "base/process/process_handle_posix.cc",
-        "base/process/process_iterator.cc",
-        "base/process/process_iterator_linux.cc",
-        "base/process/process_metrics.cc",
-        "base/process/process_metrics_linux.cc",
-        "base/process/process_metrics_posix.cc",
-        "base/process/process_posix.cc",
-        "base/profiler/arm_cfi_table.cc",
-        "base/profiler/frame.cc",
-        "base/profiler/metadata_recorder.cc",
-        "base/profiler/module_cache.cc",
-        "base/profiler/module_cache_posix.cc",
-        "base/profiler/sample_metadata.cc",
-        "base/profiler/sampling_profiler_thread_token.cc",
-        "base/profiler/stack_base_address_posix.cc",
-        "base/profiler/stack_buffer.cc",
-        "base/profiler/stack_copier.cc",
-        "base/profiler/stack_copier_signal.cc",
-        "base/profiler/stack_copier_suspend.cc",
-        "base/profiler/stack_sampler.cc",
-        "base/profiler/stack_sampler_impl.cc",
-        "base/profiler/stack_sampling_profiler.cc",
-        "base/profiler/thread_delegate_posix.cc",
-        "base/profiler/unwinder.cc",
-        "base/rand_util.cc",
-        "base/rand_util_posix.cc",
-        "base/run_loop.cc",
-        "base/sampling_heap_profiler/lock_free_address_hash_set.cc",
-        "base/sampling_heap_profiler/poisson_allocation_sampler.cc",
-        "base/sampling_heap_profiler/sampling_heap_profiler.cc",
-        "base/scoped_add_feature_flags.cc",
-        "base/scoped_environment_variable_override.cc",
-        "base/scoped_native_library.cc",
-        "base/sequence_checker.cc",
-        "base/sequence_checker_impl.cc",
-        "base/sequence_token.cc",
-        "base/strings/abseil_string_conversions.cc",
-        "base/strings/abseil_string_number_conversions.cc",
-        "base/strings/escape.cc",
-        "base/strings/latin1_string_conversions.cc",
-        "base/strings/pattern.cc",
-        "base/strings/safe_sprintf.cc",
-        "base/strings/strcat.cc",
-        "base/strings/string_number_conversions.cc",
-        "base/strings/string_piece.cc",
-        "base/strings/string_split.cc",
-        "base/strings/string_util.cc",
-        "base/strings/string_util_constants.cc",
-        "base/strings/stringprintf.cc",
-        "base/strings/sys_string_conversions_posix.cc",
-        "base/strings/utf_offset_string_conversions.cc",
-        "base/strings/utf_string_conversion_utils.cc",
-        "base/strings/utf_string_conversions.cc",
-        "base/substring_set_matcher/matcher_string_pattern.cc",
-        "base/substring_set_matcher/substring_set_matcher.cc",
-        "base/supports_user_data.cc",
-        "base/sync_socket.cc",
-        "base/sync_socket_posix.cc",
-        "base/synchronization/atomic_flag.cc",
-        "base/synchronization/condition_variable_posix.cc",
-        "base/synchronization/lock.cc",
-        "base/synchronization/lock_impl_posix.cc",
-        "base/synchronization/waitable_event_posix.cc",
-        "base/synchronization/waitable_event_watcher_posix.cc",
-        "base/syslog_logging.cc",
-        "base/system/sys_info.cc",
-        "base/system/sys_info_linux.cc",
-        "base/system/sys_info_posix.cc",
-        "base/system/system_monitor.cc",
-        "base/task/cancelable_task_tracker.cc",
-        "base/task/common/checked_lock_impl.cc",
-        "base/task/common/lazy_now.cc",
-        "base/task/common/operations_controller.cc",
-        "base/task/common/scoped_defer_task_posting.cc",
-        "base/task/common/task_annotator.cc",
-        "base/task/current_thread.cc",
-        "base/task/default_delayed_task_handle_delegate.cc",
-        "base/task/deferred_sequenced_task_runner.cc",
-        "base/task/delayed_task_handle.cc",
-        "base/task/lazy_thread_pool_task_runner.cc",
-        "base/task/post_job.cc",
-        "base/task/scoped_set_task_priority_for_current_thread.cc",
-        "base/task/sequence_manager/associated_thread_id.cc",
-        "base/task/sequence_manager/atomic_flag_set.cc",
-        "base/task/sequence_manager/delayed_task_handle_delegate.cc",
-        "base/task/sequence_manager/enqueue_order_generator.cc",
-        "base/task/sequence_manager/fence.cc",
-        "base/task/sequence_manager/hierarchical_timing_wheel.cc",
-        "base/task/sequence_manager/sequence_manager.cc",
-        "base/task/sequence_manager/sequence_manager_impl.cc",
-        "base/task/sequence_manager/sequenced_task_source.cc",
-        "base/task/sequence_manager/task_order.cc",
-        "base/task/sequence_manager/task_queue.cc",
-        "base/task/sequence_manager/task_queue_impl.cc",
-        "base/task/sequence_manager/task_queue_selector.cc",
-        "base/task/sequence_manager/tasks.cc",
-        "base/task/sequence_manager/thread_controller.cc",
-        "base/task/sequence_manager/thread_controller_impl.cc",
-        "base/task/sequence_manager/thread_controller_power_monitor.cc",
-        "base/task/sequence_manager/thread_controller_with_message_pump_impl.cc",
-        "base/task/sequence_manager/time_domain.cc",
-        "base/task/sequence_manager/timing_wheel.cc",
-        "base/task/sequence_manager/wake_up_queue.cc",
-        "base/task/sequence_manager/work_deduplicator.cc",
-        "base/task/sequence_manager/work_queue.cc",
-        "base/task/sequence_manager/work_queue_sets.cc",
-        "base/task/sequenced_task_runner.cc",
-        "base/task/simple_task_executor.cc",
-        "base/task/single_thread_task_executor.cc",
-        "base/task/single_thread_task_runner.cc",
-        "base/task/task_executor.cc",
-        "base/task/task_features.cc",
-        "base/task/task_runner.cc",
-        "base/task/task_traits.cc",
-        "base/task/thread_pool.cc",
-        "base/task/thread_pool/delayed_priority_queue.cc",
-        "base/task/thread_pool/delayed_task_manager.cc",
-        "base/task/thread_pool/environment_config.cc",
-        "base/task/thread_pool/initialization_util.cc",
-        "base/task/thread_pool/job_task_source.cc",
-        "base/task/thread_pool/pooled_parallel_task_runner.cc",
-        "base/task/thread_pool/pooled_sequenced_task_runner.cc",
-        "base/task/thread_pool/pooled_single_thread_task_runner_manager.cc",
-        "base/task/thread_pool/pooled_task_runner_delegate.cc",
-        "base/task/thread_pool/priority_queue.cc",
-        "base/task/thread_pool/sequence.cc",
-        "base/task/thread_pool/service_thread.cc",
-        "base/task/thread_pool/task.cc",
-        "base/task/thread_pool/task_source.cc",
-        "base/task/thread_pool/task_source_sort_key.cc",
-        "base/task/thread_pool/task_tracker.cc",
-        "base/task/thread_pool/thread_group.cc",
-        "base/task/thread_pool/thread_group_impl.cc",
-        "base/task/thread_pool/thread_group_native.cc",
-        "base/task/thread_pool/thread_pool_impl.cc",
-        "base/task/thread_pool/thread_pool_instance.cc",
-        "base/task/thread_pool/worker_thread.cc",
-        "base/task/thread_pool/worker_thread_stack.cc",
-        "base/third_party/cityhash/city.cc",
-        "base/third_party/cityhash_v103/src/city_v103.cc",
-        "base/third_party/nspr/prtime.cc",
-        "base/third_party/superfasthash/superfasthash.c",
-        "base/threading/hang_watcher.cc",
-        "base/threading/platform_thread.cc",
-        "base/threading/platform_thread_internal_posix.cc",
-        "base/threading/platform_thread_posix.cc",
-        "base/threading/platform_thread_ref.cc",
-        "base/threading/post_task_and_reply_impl.cc",
-        "base/threading/scoped_blocking_call.cc",
-        "base/threading/scoped_blocking_call_internal.cc",
-        "base/threading/scoped_thread_priority.cc",
-        "base/threading/sequence_local_storage_map.cc",
-        "base/threading/sequence_local_storage_slot.cc",
-        "base/threading/sequenced_task_runner_handle.cc",
-        "base/threading/simple_thread.cc",
-        "base/threading/thread.cc",
-        "base/threading/thread_checker.cc",
-        "base/threading/thread_checker_impl.cc",
-        "base/threading/thread_collision_warner.cc",
-        "base/threading/thread_id_name_manager.cc",
-        "base/threading/thread_local_storage.cc",
-        "base/threading/thread_local_storage_posix.cc",
-        "base/threading/thread_restrictions.cc",
-        "base/threading/thread_task_runner_handle.cc",
-        "base/threading/watchdog.cc",
-        "base/time/clock.cc",
-        "base/time/default_clock.cc",
-        "base/time/default_tick_clock.cc",
-        "base/time/tick_clock.cc",
-        "base/time/time.cc",
-        "base/time/time_conversion_posix.cc",
-        "base/time/time_delta_from_string.cc",
-        "base/time/time_exploded_icu.cc",
-        "base/time/time_exploded_posix.cc",
-        "base/time/time_now_posix.cc",
-        "base/time/time_override.cc",
-        "base/time/time_to_iso8601.cc",
-        "base/timer/elapsed_timer.cc",
-        "base/timer/hi_res_timer_manager_posix.cc",
-        "base/timer/lap_timer.cc",
-        "base/timer/timer.cc",
-        "base/timer/wall_clock_timer.cc",
-        "base/token.cc",
-        "base/trace_event/heap_profiler_allocation_context.cc",
-        "base/trace_event/heap_profiler_allocation_context_tracker.cc",
-        "base/trace_event/memory_allocator_dump_guid.cc",
-        "base/trace_event/trace_event_stub.cc",
-        "base/trace_event/trace_id_helper.cc",
-        "base/unguessable_token.cc",
-        "base/value_iterators.cc",
-        "base/values.cc",
-        "base/version.cc",
-        "base/vlog.cc",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DBASE_IMPLEMENTATION",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DICU_UTIL_DATA_IMPL=ICU_UTIL_DATA_FILE",
-        "-DUSE_CHROMIUM_ICU=1",
-        "-DU_ENABLE_DYLOAD=0",
-        "-DU_ENABLE_RESOURCE_TRACING=0",
-        "-DU_ENABLE_TRACING=1",
-        "-DU_STATIC_IMPLEMENTATION",
-        "-DU_USING_ICU_NAMESPACE=0",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-        "third_party/icu/source/common/",
-        "third_party/icu/source/i18n/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android: {
-            shared_libs: [
-                "libandroid",
-                "liblog",
-            ],
-        },
-        android_arm: {
-            srcs: [
-                ":cronet_aml_third_party_android_ndk_cpu_features",
-                ":cronet_aml_third_party_ashmem_ashmem",
-                "base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_linker_wrapped_symbols.cc",
-                "base/android/android_hardware_buffer_compat.cc",
-                "base/android/android_image_reader_compat.cc",
-                "base/android/apk_assets.cc",
-                "base/android/application_status_listener.cc",
-                "base/android/base_feature_list.cc",
-                "base/android/base_features.cc",
-                "base/android/base_jni_onload.cc",
-                "base/android/build_info.cc",
-                "base/android/bundle_utils.cc",
-                "base/android/callback_android.cc",
-                "base/android/child_process_service.cc",
-                "base/android/command_line_android.cc",
-                "base/android/content_uri_utils.cc",
-                "base/android/cpu_features.cc",
-                "base/android/early_trace_event_binding.cc",
-                "base/android/event_log.cc",
-                "base/android/feature_list_jni.cc",
-                "base/android/features_jni.cc",
-                "base/android/field_trial_list.cc",
-                "base/android/important_file_writer_android.cc",
-                "base/android/int_string_callback.cc",
-                "base/android/jank_metric_uma_recorder.cc",
-                "base/android/java_exception_reporter.cc",
-                "base/android/java_handler_thread.cc",
-                "base/android/java_heap_dump_generator.cc",
-                "base/android/java_runtime.cc",
-                "base/android/jni_android.cc",
-                "base/android/jni_array.cc",
-                "base/android/jni_registrar.cc",
-                "base/android/jni_string.cc",
-                "base/android/jni_utils.cc",
-                "base/android/jni_weak_ref.cc",
-                "base/android/library_loader/anchor_functions.cc",
-                "base/android/library_loader/library_loader_hooks.cc",
-                "base/android/library_loader/library_prefetcher.cc",
-                "base/android/library_loader/library_prefetcher_hooks.cc",
-                "base/android/locale_utils.cc",
-                "base/android/memory_pressure_listener_android.cc",
-                "base/android/native_uma_recorder.cc",
-                "base/android/path_service_android.cc",
-                "base/android/path_utils.cc",
-                "base/android/radio_utils.cc",
-                "base/android/reached_addresses_bitset.cc",
-                "base/android/reached_code_profiler.cc",
-                "base/android/remove_stale_data.cc",
-                "base/android/scoped_hardware_buffer_fence_sync.cc",
-                "base/android/scoped_hardware_buffer_handle.cc",
-                "base/android/scoped_java_ref.cc",
-                "base/android/statistics_recorder_android.cc",
-                "base/android/sys_utils.cc",
-                "base/android/task_scheduler/post_task_android.cc",
-                "base/android/task_scheduler/task_runner_android.cc",
-                "base/android/thread_instruction_count.cc",
-                "base/android/timezone_utils.cc",
-                "base/android/trace_event_binding.cc",
-                "base/android/unguessable_token_android.cc",
-                "base/base_paths_android.cc",
-                "base/debug/stack_trace_android.cc",
-                "base/files/file_util_android.cc",
-                "base/files/scoped_file_android.cc",
-                "base/memory/platform_shared_memory_mapper_android.cc",
-                "base/memory/platform_shared_memory_region_android.cc",
-                "base/message_loop/message_pump_android.cc",
-                "base/os_compat_android.cc",
-                "base/power_monitor/power_monitor_device_source_android.cc",
-                "base/process/process_android.cc",
-                "base/profiler/chrome_unwind_info_android.cc",
-                "base/profiler/chrome_unwinder_android.cc",
-                "base/profiler/chrome_unwinder_android_v2.cc",
-                "base/profiler/stack_sampler_android.cc",
-                "base/system/sys_info_android.cc",
-                "base/threading/platform_thread_android.cc",
-                "base/time/time_android.cc",
-                "base/trace_event/cfi_backtrace_android.cc",
-            ],
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            local_include_dirs: [
-                "third_party/android_ndk/sources/android/cpufeatures/",
-            ],
-            generated_headers: [
-                "cronet_aml_base_allocator_buildflags__android_arm",
-                "cronet_aml_base_anchor_functions_buildflags__android_arm",
-                "cronet_aml_base_android_runtime_jni_headers__android_arm",
-                "cronet_aml_base_base_jni_headers__android_arm",
-                "cronet_aml_base_build_date__android_arm",
-                "cronet_aml_base_cfi_buildflags__android_arm",
-                "cronet_aml_base_clang_profiling_buildflags__android_arm",
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_feature_list_buildflags__android_arm",
-                "cronet_aml_base_ios_cronet_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_base_message_pump_buildflags__android_arm",
-                "cronet_aml_base_orderfile_buildflags__android_arm",
-                "cronet_aml_base_parsing_buildflags__android_arm",
-                "cronet_aml_base_power_monitor_buildflags__android_arm",
-                "cronet_aml_base_profiler_buildflags__android_arm",
-                "cronet_aml_base_sanitizer_buildflags__android_arm",
-                "cronet_aml_base_synchronization_buildflags__android_arm",
-                "cronet_aml_base_tracing_buildflags__android_arm",
-                "cronet_aml_build_branding_buildflags__android_arm",
-                "cronet_aml_build_chromecast_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_build_config_compiler_compiler_buildflags__android_arm",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_allocator_buildflags__android_arm",
-                "cronet_aml_base_anchor_functions_buildflags__android_arm",
-                "cronet_aml_base_android_runtime_jni_headers__android_arm",
-                "cronet_aml_base_base_jni_headers__android_arm",
-                "cronet_aml_base_build_date__android_arm",
-                "cronet_aml_base_cfi_buildflags__android_arm",
-                "cronet_aml_base_clang_profiling_buildflags__android_arm",
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_feature_list_buildflags__android_arm",
-                "cronet_aml_base_ios_cronet_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_base_message_pump_buildflags__android_arm",
-                "cronet_aml_base_orderfile_buildflags__android_arm",
-                "cronet_aml_base_parsing_buildflags__android_arm",
-                "cronet_aml_base_power_monitor_buildflags__android_arm",
-                "cronet_aml_base_profiler_buildflags__android_arm",
-                "cronet_aml_base_sanitizer_buildflags__android_arm",
-                "cronet_aml_base_synchronization_buildflags__android_arm",
-                "cronet_aml_base_tracing_buildflags__android_arm",
-                "cronet_aml_build_branding_buildflags__android_arm",
-                "cronet_aml_build_chromecast_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_build_config_compiler_compiler_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            srcs: [
-                ":cronet_aml_third_party_android_ndk_cpu_features",
-                ":cronet_aml_third_party_ashmem_ashmem",
-                "base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_linker_wrapped_symbols.cc",
-                "base/android/android_hardware_buffer_compat.cc",
-                "base/android/android_image_reader_compat.cc",
-                "base/android/apk_assets.cc",
-                "base/android/application_status_listener.cc",
-                "base/android/base_feature_list.cc",
-                "base/android/base_features.cc",
-                "base/android/base_jni_onload.cc",
-                "base/android/build_info.cc",
-                "base/android/bundle_utils.cc",
-                "base/android/callback_android.cc",
-                "base/android/child_process_service.cc",
-                "base/android/command_line_android.cc",
-                "base/android/content_uri_utils.cc",
-                "base/android/cpu_features.cc",
-                "base/android/early_trace_event_binding.cc",
-                "base/android/event_log.cc",
-                "base/android/feature_list_jni.cc",
-                "base/android/features_jni.cc",
-                "base/android/field_trial_list.cc",
-                "base/android/important_file_writer_android.cc",
-                "base/android/int_string_callback.cc",
-                "base/android/jank_metric_uma_recorder.cc",
-                "base/android/java_exception_reporter.cc",
-                "base/android/java_handler_thread.cc",
-                "base/android/java_heap_dump_generator.cc",
-                "base/android/java_runtime.cc",
-                "base/android/jni_android.cc",
-                "base/android/jni_array.cc",
-                "base/android/jni_registrar.cc",
-                "base/android/jni_string.cc",
-                "base/android/jni_utils.cc",
-                "base/android/jni_weak_ref.cc",
-                "base/android/library_loader/anchor_functions.cc",
-                "base/android/library_loader/library_loader_hooks.cc",
-                "base/android/library_loader/library_prefetcher.cc",
-                "base/android/library_loader/library_prefetcher_hooks.cc",
-                "base/android/locale_utils.cc",
-                "base/android/memory_pressure_listener_android.cc",
-                "base/android/native_uma_recorder.cc",
-                "base/android/path_service_android.cc",
-                "base/android/path_utils.cc",
-                "base/android/radio_utils.cc",
-                "base/android/reached_addresses_bitset.cc",
-                "base/android/reached_code_profiler.cc",
-                "base/android/remove_stale_data.cc",
-                "base/android/scoped_hardware_buffer_fence_sync.cc",
-                "base/android/scoped_hardware_buffer_handle.cc",
-                "base/android/scoped_java_ref.cc",
-                "base/android/statistics_recorder_android.cc",
-                "base/android/sys_utils.cc",
-                "base/android/task_scheduler/post_task_android.cc",
-                "base/android/task_scheduler/task_runner_android.cc",
-                "base/android/thread_instruction_count.cc",
-                "base/android/timezone_utils.cc",
-                "base/android/trace_event_binding.cc",
-                "base/android/unguessable_token_android.cc",
-                "base/base_paths_android.cc",
-                "base/debug/stack_trace_android.cc",
-                "base/files/file_util_android.cc",
-                "base/files/scoped_file_android.cc",
-                "base/memory/platform_shared_memory_mapper_android.cc",
-                "base/memory/platform_shared_memory_region_android.cc",
-                "base/message_loop/message_pump_android.cc",
-                "base/os_compat_android.cc",
-                "base/power_monitor/power_monitor_device_source_android.cc",
-                "base/process/process_android.cc",
-                "base/profiler/stack_sampler_android.cc",
-                "base/system/sys_info_android.cc",
-                "base/threading/platform_thread_android.cc",
-                "base/time/time_android.cc",
-            ],
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            local_include_dirs: [
-                "third_party/android_ndk/sources/android/cpufeatures/",
-            ],
-            generated_headers: [
-                "cronet_aml_base_allocator_buildflags__android_arm64",
-                "cronet_aml_base_anchor_functions_buildflags__android_arm64",
-                "cronet_aml_base_android_runtime_jni_headers__android_arm64",
-                "cronet_aml_base_base_jni_headers__android_arm64",
-                "cronet_aml_base_build_date__android_arm64",
-                "cronet_aml_base_cfi_buildflags__android_arm64",
-                "cronet_aml_base_clang_profiling_buildflags__android_arm64",
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_feature_list_buildflags__android_arm64",
-                "cronet_aml_base_ios_cronet_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_base_message_pump_buildflags__android_arm64",
-                "cronet_aml_base_orderfile_buildflags__android_arm64",
-                "cronet_aml_base_parsing_buildflags__android_arm64",
-                "cronet_aml_base_power_monitor_buildflags__android_arm64",
-                "cronet_aml_base_profiler_buildflags__android_arm64",
-                "cronet_aml_base_sanitizer_buildflags__android_arm64",
-                "cronet_aml_base_synchronization_buildflags__android_arm64",
-                "cronet_aml_base_tracing_buildflags__android_arm64",
-                "cronet_aml_build_branding_buildflags__android_arm64",
-                "cronet_aml_build_chromecast_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_build_config_compiler_compiler_buildflags__android_arm64",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_allocator_buildflags__android_arm64",
-                "cronet_aml_base_anchor_functions_buildflags__android_arm64",
-                "cronet_aml_base_android_runtime_jni_headers__android_arm64",
-                "cronet_aml_base_base_jni_headers__android_arm64",
-                "cronet_aml_base_build_date__android_arm64",
-                "cronet_aml_base_cfi_buildflags__android_arm64",
-                "cronet_aml_base_clang_profiling_buildflags__android_arm64",
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_feature_list_buildflags__android_arm64",
-                "cronet_aml_base_ios_cronet_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_base_message_pump_buildflags__android_arm64",
-                "cronet_aml_base_orderfile_buildflags__android_arm64",
-                "cronet_aml_base_parsing_buildflags__android_arm64",
-                "cronet_aml_base_power_monitor_buildflags__android_arm64",
-                "cronet_aml_base_profiler_buildflags__android_arm64",
-                "cronet_aml_base_sanitizer_buildflags__android_arm64",
-                "cronet_aml_base_synchronization_buildflags__android_arm64",
-                "cronet_aml_base_tracing_buildflags__android_arm64",
-                "cronet_aml_build_branding_buildflags__android_arm64",
-                "cronet_aml_build_chromecast_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_build_config_compiler_compiler_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            srcs: [
-                ":cronet_aml_third_party_android_ndk_cpu_features",
-                ":cronet_aml_third_party_ashmem_ashmem",
-                "base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_linker_wrapped_symbols.cc",
-                "base/android/android_hardware_buffer_compat.cc",
-                "base/android/android_image_reader_compat.cc",
-                "base/android/apk_assets.cc",
-                "base/android/application_status_listener.cc",
-                "base/android/base_feature_list.cc",
-                "base/android/base_features.cc",
-                "base/android/base_jni_onload.cc",
-                "base/android/build_info.cc",
-                "base/android/bundle_utils.cc",
-                "base/android/callback_android.cc",
-                "base/android/child_process_service.cc",
-                "base/android/command_line_android.cc",
-                "base/android/content_uri_utils.cc",
-                "base/android/cpu_features.cc",
-                "base/android/early_trace_event_binding.cc",
-                "base/android/event_log.cc",
-                "base/android/feature_list_jni.cc",
-                "base/android/features_jni.cc",
-                "base/android/field_trial_list.cc",
-                "base/android/important_file_writer_android.cc",
-                "base/android/int_string_callback.cc",
-                "base/android/jank_metric_uma_recorder.cc",
-                "base/android/java_exception_reporter.cc",
-                "base/android/java_handler_thread.cc",
-                "base/android/java_heap_dump_generator.cc",
-                "base/android/java_runtime.cc",
-                "base/android/jni_android.cc",
-                "base/android/jni_array.cc",
-                "base/android/jni_registrar.cc",
-                "base/android/jni_string.cc",
-                "base/android/jni_utils.cc",
-                "base/android/jni_weak_ref.cc",
-                "base/android/library_loader/anchor_functions.cc",
-                "base/android/library_loader/library_loader_hooks.cc",
-                "base/android/library_loader/library_prefetcher.cc",
-                "base/android/library_loader/library_prefetcher_hooks.cc",
-                "base/android/locale_utils.cc",
-                "base/android/memory_pressure_listener_android.cc",
-                "base/android/native_uma_recorder.cc",
-                "base/android/path_service_android.cc",
-                "base/android/path_utils.cc",
-                "base/android/radio_utils.cc",
-                "base/android/reached_addresses_bitset.cc",
-                "base/android/reached_code_profiler_stub.cc",
-                "base/android/remove_stale_data.cc",
-                "base/android/scoped_hardware_buffer_fence_sync.cc",
-                "base/android/scoped_hardware_buffer_handle.cc",
-                "base/android/scoped_java_ref.cc",
-                "base/android/statistics_recorder_android.cc",
-                "base/android/sys_utils.cc",
-                "base/android/task_scheduler/post_task_android.cc",
-                "base/android/task_scheduler/task_runner_android.cc",
-                "base/android/thread_instruction_count.cc",
-                "base/android/timezone_utils.cc",
-                "base/android/trace_event_binding.cc",
-                "base/android/unguessable_token_android.cc",
-                "base/base_paths_android.cc",
-                "base/debug/stack_trace_android.cc",
-                "base/files/file_util_android.cc",
-                "base/files/scoped_file_android.cc",
-                "base/memory/platform_shared_memory_mapper_android.cc",
-                "base/memory/platform_shared_memory_region_android.cc",
-                "base/message_loop/message_pump_android.cc",
-                "base/os_compat_android.cc",
-                "base/power_monitor/power_monitor_device_source_android.cc",
-                "base/process/process_android.cc",
-                "base/profiler/stack_sampler_android.cc",
-                "base/system/sys_info_android.cc",
-                "base/threading/platform_thread_android.cc",
-                "base/time/time_android.cc",
-            ],
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            local_include_dirs: [
-                "third_party/android_ndk/sources/android/cpufeatures/",
-            ],
-            generated_headers: [
-                "cronet_aml_base_allocator_buildflags__android_x86",
-                "cronet_aml_base_anchor_functions_buildflags__android_x86",
-                "cronet_aml_base_android_runtime_jni_headers__android_x86",
-                "cronet_aml_base_base_jni_headers__android_x86",
-                "cronet_aml_base_build_date__android_x86",
-                "cronet_aml_base_cfi_buildflags__android_x86",
-                "cronet_aml_base_clang_profiling_buildflags__android_x86",
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_feature_list_buildflags__android_x86",
-                "cronet_aml_base_ios_cronet_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_base_message_pump_buildflags__android_x86",
-                "cronet_aml_base_orderfile_buildflags__android_x86",
-                "cronet_aml_base_parsing_buildflags__android_x86",
-                "cronet_aml_base_power_monitor_buildflags__android_x86",
-                "cronet_aml_base_profiler_buildflags__android_x86",
-                "cronet_aml_base_sanitizer_buildflags__android_x86",
-                "cronet_aml_base_synchronization_buildflags__android_x86",
-                "cronet_aml_base_tracing_buildflags__android_x86",
-                "cronet_aml_build_branding_buildflags__android_x86",
-                "cronet_aml_build_chromecast_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_build_config_compiler_compiler_buildflags__android_x86",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_allocator_buildflags__android_x86",
-                "cronet_aml_base_anchor_functions_buildflags__android_x86",
-                "cronet_aml_base_android_runtime_jni_headers__android_x86",
-                "cronet_aml_base_base_jni_headers__android_x86",
-                "cronet_aml_base_build_date__android_x86",
-                "cronet_aml_base_cfi_buildflags__android_x86",
-                "cronet_aml_base_clang_profiling_buildflags__android_x86",
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_feature_list_buildflags__android_x86",
-                "cronet_aml_base_ios_cronet_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_base_message_pump_buildflags__android_x86",
-                "cronet_aml_base_orderfile_buildflags__android_x86",
-                "cronet_aml_base_parsing_buildflags__android_x86",
-                "cronet_aml_base_power_monitor_buildflags__android_x86",
-                "cronet_aml_base_profiler_buildflags__android_x86",
-                "cronet_aml_base_sanitizer_buildflags__android_x86",
-                "cronet_aml_base_synchronization_buildflags__android_x86",
-                "cronet_aml_base_tracing_buildflags__android_x86",
-                "cronet_aml_build_branding_buildflags__android_x86",
-                "cronet_aml_build_chromecast_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_build_config_compiler_compiler_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            srcs: [
-                ":cronet_aml_third_party_android_ndk_cpu_features",
-                ":cronet_aml_third_party_ashmem_ashmem",
-                "base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_linker_wrapped_symbols.cc",
-                "base/android/android_hardware_buffer_compat.cc",
-                "base/android/android_image_reader_compat.cc",
-                "base/android/apk_assets.cc",
-                "base/android/application_status_listener.cc",
-                "base/android/base_feature_list.cc",
-                "base/android/base_features.cc",
-                "base/android/base_jni_onload.cc",
-                "base/android/build_info.cc",
-                "base/android/bundle_utils.cc",
-                "base/android/callback_android.cc",
-                "base/android/child_process_service.cc",
-                "base/android/command_line_android.cc",
-                "base/android/content_uri_utils.cc",
-                "base/android/cpu_features.cc",
-                "base/android/early_trace_event_binding.cc",
-                "base/android/event_log.cc",
-                "base/android/feature_list_jni.cc",
-                "base/android/features_jni.cc",
-                "base/android/field_trial_list.cc",
-                "base/android/important_file_writer_android.cc",
-                "base/android/int_string_callback.cc",
-                "base/android/jank_metric_uma_recorder.cc",
-                "base/android/java_exception_reporter.cc",
-                "base/android/java_handler_thread.cc",
-                "base/android/java_heap_dump_generator.cc",
-                "base/android/java_runtime.cc",
-                "base/android/jni_android.cc",
-                "base/android/jni_array.cc",
-                "base/android/jni_registrar.cc",
-                "base/android/jni_string.cc",
-                "base/android/jni_utils.cc",
-                "base/android/jni_weak_ref.cc",
-                "base/android/library_loader/anchor_functions.cc",
-                "base/android/library_loader/library_loader_hooks.cc",
-                "base/android/library_loader/library_prefetcher.cc",
-                "base/android/library_loader/library_prefetcher_hooks.cc",
-                "base/android/locale_utils.cc",
-                "base/android/memory_pressure_listener_android.cc",
-                "base/android/native_uma_recorder.cc",
-                "base/android/path_service_android.cc",
-                "base/android/path_utils.cc",
-                "base/android/radio_utils.cc",
-                "base/android/reached_addresses_bitset.cc",
-                "base/android/reached_code_profiler_stub.cc",
-                "base/android/remove_stale_data.cc",
-                "base/android/scoped_hardware_buffer_fence_sync.cc",
-                "base/android/scoped_hardware_buffer_handle.cc",
-                "base/android/scoped_java_ref.cc",
-                "base/android/statistics_recorder_android.cc",
-                "base/android/sys_utils.cc",
-                "base/android/task_scheduler/post_task_android.cc",
-                "base/android/task_scheduler/task_runner_android.cc",
-                "base/android/thread_instruction_count.cc",
-                "base/android/timezone_utils.cc",
-                "base/android/trace_event_binding.cc",
-                "base/android/unguessable_token_android.cc",
-                "base/base_paths_android.cc",
-                "base/debug/stack_trace_android.cc",
-                "base/files/file_util_android.cc",
-                "base/files/scoped_file_android.cc",
-                "base/memory/platform_shared_memory_mapper_android.cc",
-                "base/memory/platform_shared_memory_region_android.cc",
-                "base/message_loop/message_pump_android.cc",
-                "base/os_compat_android.cc",
-                "base/power_monitor/power_monitor_device_source_android.cc",
-                "base/process/process_android.cc",
-                "base/profiler/stack_sampler_android.cc",
-                "base/system/sys_info_android.cc",
-                "base/threading/platform_thread_android.cc",
-                "base/time/time_android.cc",
-            ],
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            local_include_dirs: [
-                "third_party/android_ndk/sources/android/cpufeatures/",
-            ],
-            generated_headers: [
-                "cronet_aml_base_allocator_buildflags__android_x86_64",
-                "cronet_aml_base_anchor_functions_buildflags__android_x86_64",
-                "cronet_aml_base_android_runtime_jni_headers__android_x86_64",
-                "cronet_aml_base_base_jni_headers__android_x86_64",
-                "cronet_aml_base_build_date__android_x86_64",
-                "cronet_aml_base_cfi_buildflags__android_x86_64",
-                "cronet_aml_base_clang_profiling_buildflags__android_x86_64",
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_feature_list_buildflags__android_x86_64",
-                "cronet_aml_base_ios_cronet_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_base_message_pump_buildflags__android_x86_64",
-                "cronet_aml_base_orderfile_buildflags__android_x86_64",
-                "cronet_aml_base_parsing_buildflags__android_x86_64",
-                "cronet_aml_base_power_monitor_buildflags__android_x86_64",
-                "cronet_aml_base_profiler_buildflags__android_x86_64",
-                "cronet_aml_base_sanitizer_buildflags__android_x86_64",
-                "cronet_aml_base_synchronization_buildflags__android_x86_64",
-                "cronet_aml_base_tracing_buildflags__android_x86_64",
-                "cronet_aml_build_branding_buildflags__android_x86_64",
-                "cronet_aml_build_chromecast_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_build_config_compiler_compiler_buildflags__android_x86_64",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_allocator_buildflags__android_x86_64",
-                "cronet_aml_base_anchor_functions_buildflags__android_x86_64",
-                "cronet_aml_base_android_runtime_jni_headers__android_x86_64",
-                "cronet_aml_base_base_jni_headers__android_x86_64",
-                "cronet_aml_base_build_date__android_x86_64",
-                "cronet_aml_base_cfi_buildflags__android_x86_64",
-                "cronet_aml_base_clang_profiling_buildflags__android_x86_64",
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_feature_list_buildflags__android_x86_64",
-                "cronet_aml_base_ios_cronet_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_base_message_pump_buildflags__android_x86_64",
-                "cronet_aml_base_orderfile_buildflags__android_x86_64",
-                "cronet_aml_base_parsing_buildflags__android_x86_64",
-                "cronet_aml_base_power_monitor_buildflags__android_x86_64",
-                "cronet_aml_base_profiler_buildflags__android_x86_64",
-                "cronet_aml_base_sanitizer_buildflags__android_x86_64",
-                "cronet_aml_base_synchronization_buildflags__android_x86_64",
-                "cronet_aml_base_tracing_buildflags__android_x86_64",
-                "cronet_aml_build_branding_buildflags__android_x86_64",
-                "cronet_aml_build_chromecast_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_build_config_compiler_compiler_buildflags__android_x86_64",
-            ],
-        },
-        host: {
-            srcs: [
-                "base/allocator/partition_allocator/shim/allocator_shim_default_dispatch_to_glibc.cc",
-                "base/base_paths_posix.cc",
-                "base/debug/stack_trace_posix.cc",
-                "base/files/file_util_linux.cc",
-                "base/files/scoped_file_linux.cc",
-                "base/memory/platform_shared_memory_mapper_posix.cc",
-                "base/memory/platform_shared_memory_region_posix.cc",
-                "base/nix/mime_util_xdg.cc",
-                "base/nix/xdg_util.cc",
-                "base/power_monitor/power_monitor_device_source_stub.cc",
-                "base/process/process_linux.cc",
-                "base/profiler/stack_sampler_posix.cc",
-                "base/stack_canary_linux.cc",
-                "base/threading/platform_thread_linux.cc",
-            ],
-            static_libs: [
-                "cronet_aml_base_third_party_symbolize_symbolize",
-                "cronet_aml_base_third_party_xdg_mime_xdg_mime",
-                "cronet_aml_base_third_party_xdg_user_dirs_xdg_user_dirs",
-            ],
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DGLOG_EXPORT=",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_SYMBOLIZE",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_allocator_buildflags__host",
-                "cronet_aml_base_anchor_functions_buildflags__host",
-                "cronet_aml_base_build_date__host",
-                "cronet_aml_base_cfi_buildflags__host",
-                "cronet_aml_base_clang_profiling_buildflags__host",
-                "cronet_aml_base_debugging_buildflags__host",
-                "cronet_aml_base_feature_list_buildflags__host",
-                "cronet_aml_base_ios_cronet_buildflags__host",
-                "cronet_aml_base_logging_buildflags__host",
-                "cronet_aml_base_message_pump_buildflags__host",
-                "cronet_aml_base_orderfile_buildflags__host",
-                "cronet_aml_base_parsing_buildflags__host",
-                "cronet_aml_base_power_monitor_buildflags__host",
-                "cronet_aml_base_profiler_buildflags__host",
-                "cronet_aml_base_sanitizer_buildflags__host",
-                "cronet_aml_base_synchronization_buildflags__host",
-                "cronet_aml_base_tracing_buildflags__host",
-                "cronet_aml_build_branding_buildflags__host",
-                "cronet_aml_build_chromecast_buildflags__host",
-                "cronet_aml_build_chromeos_buildflags__host",
-                "cronet_aml_build_config_compiler_compiler_buildflags__host",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_allocator_buildflags__host",
-                "cronet_aml_base_anchor_functions_buildflags__host",
-                "cronet_aml_base_build_date__host",
-                "cronet_aml_base_cfi_buildflags__host",
-                "cronet_aml_base_clang_profiling_buildflags__host",
-                "cronet_aml_base_debugging_buildflags__host",
-                "cronet_aml_base_feature_list_buildflags__host",
-                "cronet_aml_base_ios_cronet_buildflags__host",
-                "cronet_aml_base_logging_buildflags__host",
-                "cronet_aml_base_message_pump_buildflags__host",
-                "cronet_aml_base_orderfile_buildflags__host",
-                "cronet_aml_base_parsing_buildflags__host",
-                "cronet_aml_base_power_monitor_buildflags__host",
-                "cronet_aml_base_profiler_buildflags__host",
-                "cronet_aml_base_sanitizer_buildflags__host",
-                "cronet_aml_base_synchronization_buildflags__host",
-                "cronet_aml_base_tracing_buildflags__host",
-                "cronet_aml_build_branding_buildflags__host",
-                "cronet_aml_build_chromecast_buildflags__host",
-                "cronet_aml_build_chromeos_buildflags__host",
-                "cronet_aml_build_config_compiler_compiler_buildflags__host",
-            ],
-        },
-    },
-}
-
-// GN: //base:base_android_java_enums_srcjar
-java_genrule {
-    name: "cronet_aml_base_base_android_java_enums_srcjar",
-    cmd: "$(location build/android/gyp/java_cpp_enum.py) --srcjar " +
-         "$(out) " +
-         "$(location base/android/application_status_listener.h) " +
-         "$(location base/android/child_process_binding_types.h) " +
-         "$(location base/android/library_loader/library_loader_hooks.h) " +
-         "$(location base/android/linker/modern_linker_jni.h) " +
-         "$(location base/android/task_scheduler/task_runner_android.h) " +
-         "$(location base/memory/memory_pressure_listener.h) " +
-         "$(location base/metrics/histogram_base.h) " +
-         "$(location base/task/task_traits.h)",
-    out: [
-        "base/base_android_java_enums_srcjar.srcjar",
-    ],
-    tool_files: [
-        "base/android/application_status_listener.h",
-        "base/android/child_process_binding_types.h",
-        "base/android/library_loader/library_loader_hooks.h",
-        "base/android/linker/modern_linker_jni.h",
-        "base/android/task_scheduler/task_runner_android.h",
-        "base/memory/memory_pressure_listener.h",
-        "base/metrics/histogram_base.h",
-        "base/task/task_traits.h",
-        "build/android/gyp/java_cpp_enum.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/android/gyp/util/java_cpp_utils.py",
-        "build/gn_helpers.py",
-    ],
-}
-
-// GN: //base:base_jni_headers__android_arm
-cc_genrule {
-    name: "cronet_aml_base_base_jni_headers__android_arm",
-    srcs: [
-        "base/android/java/src/org/chromium/base/ApkAssets.java",
-        "base/android/java/src/org/chromium/base/ApplicationStatus.java",
-        "base/android/java/src/org/chromium/base/BaseFeatureList.java",
-        "base/android/java/src/org/chromium/base/BuildInfo.java",
-        "base/android/java/src/org/chromium/base/BundleUtils.java",
-        "base/android/java/src/org/chromium/base/Callback.java",
-        "base/android/java/src/org/chromium/base/CommandLine.java",
-        "base/android/java/src/org/chromium/base/ContentUriUtils.java",
-        "base/android/java/src/org/chromium/base/CpuFeatures.java",
-        "base/android/java/src/org/chromium/base/EarlyTraceEvent.java",
-        "base/android/java/src/org/chromium/base/EventLog.java",
-        "base/android/java/src/org/chromium/base/FeatureList.java",
-        "base/android/java/src/org/chromium/base/Features.java",
-        "base/android/java/src/org/chromium/base/FieldTrialList.java",
-        "base/android/java/src/org/chromium/base/FileUtils.java",
-        "base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java",
-        "base/android/java/src/org/chromium/base/IntStringCallback.java",
-        "base/android/java/src/org/chromium/base/JNIUtils.java",
-        "base/android/java/src/org/chromium/base/JavaExceptionReporter.java",
-        "base/android/java/src/org/chromium/base/JavaHandlerThread.java",
-        "base/android/java/src/org/chromium/base/LocaleUtils.java",
-        "base/android/java/src/org/chromium/base/MemoryPressureListener.java",
-        "base/android/java/src/org/chromium/base/PathService.java",
-        "base/android/java/src/org/chromium/base/PathUtils.java",
-        "base/android/java/src/org/chromium/base/PowerMonitor.java",
-        "base/android/java/src/org/chromium/base/RadioUtils.java",
-        "base/android/java/src/org/chromium/base/SysUtils.java",
-        "base/android/java/src/org/chromium/base/ThreadUtils.java",
-        "base/android/java/src/org/chromium/base/TimezoneUtils.java",
-        "base/android/java/src/org/chromium/base/TraceEvent.java",
-        "base/android/java/src/org/chromium/base/UnguessableToken.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java",
-        "base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
-        "base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java",
-        "base/android/java/src/org/chromium/base/memory/JavaHeapDumpGenerator.java",
-        "base/android/java/src/org/chromium/base/metrics/NativeUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java",
-        "base/android/java/src/org/chromium/base/task/PostTask.java",
-        "base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/base/base_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "ApkAssets_jni.h " +
-         "--output_name " +
-         "ApplicationStatus_jni.h " +
-         "--output_name " +
-         "BaseFeatureList_jni.h " +
-         "--output_name " +
-         "BuildInfo_jni.h " +
-         "--output_name " +
-         "BundleUtils_jni.h " +
-         "--output_name " +
-         "Callback_jni.h " +
-         "--output_name " +
-         "CommandLine_jni.h " +
-         "--output_name " +
-         "ContentUriUtils_jni.h " +
-         "--output_name " +
-         "CpuFeatures_jni.h " +
-         "--output_name " +
-         "EarlyTraceEvent_jni.h " +
-         "--output_name " +
-         "EventLog_jni.h " +
-         "--output_name " +
-         "FeatureList_jni.h " +
-         "--output_name " +
-         "Features_jni.h " +
-         "--output_name " +
-         "FieldTrialList_jni.h " +
-         "--output_name " +
-         "FileUtils_jni.h " +
-         "--output_name " +
-         "ImportantFileWriterAndroid_jni.h " +
-         "--output_name " +
-         "IntStringCallback_jni.h " +
-         "--output_name " +
-         "JNIUtils_jni.h " +
-         "--output_name " +
-         "JavaExceptionReporter_jni.h " +
-         "--output_name " +
-         "JavaHandlerThread_jni.h " +
-         "--output_name " +
-         "LocaleUtils_jni.h " +
-         "--output_name " +
-         "MemoryPressureListener_jni.h " +
-         "--output_name " +
-         "PathService_jni.h " +
-         "--output_name " +
-         "PathUtils_jni.h " +
-         "--output_name " +
-         "PowerMonitor_jni.h " +
-         "--output_name " +
-         "RadioUtils_jni.h " +
-         "--output_name " +
-         "SysUtils_jni.h " +
-         "--output_name " +
-         "ThreadUtils_jni.h " +
-         "--output_name " +
-         "TimezoneUtils_jni.h " +
-         "--output_name " +
-         "TraceEvent_jni.h " +
-         "--output_name " +
-         "UnguessableToken_jni.h " +
-         "--output_name " +
-         "JankMetricUMARecorder_jni.h " +
-         "--output_name " +
-         "LibraryLoader_jni.h " +
-         "--output_name " +
-         "LibraryPrefetcher_jni.h " +
-         "--output_name " +
-         "JavaHeapDumpGenerator_jni.h " +
-         "--output_name " +
-         "NativeUmaRecorder_jni.h " +
-         "--output_name " +
-         "StatisticsRecorderAndroid_jni.h " +
-         "--output_name " +
-         "ChildProcessService_jni.h " +
-         "--output_name " +
-         "PostTask_jni.h " +
-         "--output_name " +
-         "TaskRunnerImpl_jni.h " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ApkAssets.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ApplicationStatus.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/BaseFeatureList.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/BuildInfo.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/BundleUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/Callback.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/CommandLine.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ContentUriUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/CpuFeatures.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/EarlyTraceEvent.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/EventLog.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/FeatureList.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/Features.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/FieldTrialList.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/FileUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/IntStringCallback.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/JNIUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/JavaExceptionReporter.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/JavaHandlerThread.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/LocaleUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/MemoryPressureListener.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/PathService.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/PathUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/PowerMonitor.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/RadioUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/SysUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ThreadUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/TimezoneUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/TraceEvent.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/UnguessableToken.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/memory/JavaHeapDumpGenerator.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/metrics/NativeUmaRecorder.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/task/PostTask.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java)",
-    out: [
-        "base/base_jni_headers/ApkAssets_jni.h",
-        "base/base_jni_headers/ApplicationStatus_jni.h",
-        "base/base_jni_headers/BaseFeatureList_jni.h",
-        "base/base_jni_headers/BuildInfo_jni.h",
-        "base/base_jni_headers/BundleUtils_jni.h",
-        "base/base_jni_headers/Callback_jni.h",
-        "base/base_jni_headers/ChildProcessService_jni.h",
-        "base/base_jni_headers/CommandLine_jni.h",
-        "base/base_jni_headers/ContentUriUtils_jni.h",
-        "base/base_jni_headers/CpuFeatures_jni.h",
-        "base/base_jni_headers/EarlyTraceEvent_jni.h",
-        "base/base_jni_headers/EventLog_jni.h",
-        "base/base_jni_headers/FeatureList_jni.h",
-        "base/base_jni_headers/Features_jni.h",
-        "base/base_jni_headers/FieldTrialList_jni.h",
-        "base/base_jni_headers/FileUtils_jni.h",
-        "base/base_jni_headers/ImportantFileWriterAndroid_jni.h",
-        "base/base_jni_headers/IntStringCallback_jni.h",
-        "base/base_jni_headers/JNIUtils_jni.h",
-        "base/base_jni_headers/JankMetricUMARecorder_jni.h",
-        "base/base_jni_headers/JavaExceptionReporter_jni.h",
-        "base/base_jni_headers/JavaHandlerThread_jni.h",
-        "base/base_jni_headers/JavaHeapDumpGenerator_jni.h",
-        "base/base_jni_headers/LibraryLoader_jni.h",
-        "base/base_jni_headers/LibraryPrefetcher_jni.h",
-        "base/base_jni_headers/LocaleUtils_jni.h",
-        "base/base_jni_headers/MemoryPressureListener_jni.h",
-        "base/base_jni_headers/NativeUmaRecorder_jni.h",
-        "base/base_jni_headers/PathService_jni.h",
-        "base/base_jni_headers/PathUtils_jni.h",
-        "base/base_jni_headers/PostTask_jni.h",
-        "base/base_jni_headers/PowerMonitor_jni.h",
-        "base/base_jni_headers/RadioUtils_jni.h",
-        "base/base_jni_headers/StatisticsRecorderAndroid_jni.h",
-        "base/base_jni_headers/SysUtils_jni.h",
-        "base/base_jni_headers/TaskRunnerImpl_jni.h",
-        "base/base_jni_headers/ThreadUtils_jni.h",
-        "base/base_jni_headers/TimezoneUtils_jni.h",
-        "base/base_jni_headers/TraceEvent_jni.h",
-        "base/base_jni_headers/UnguessableToken_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:base_jni_headers__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_base_jni_headers__android_arm64",
-    srcs: [
-        "base/android/java/src/org/chromium/base/ApkAssets.java",
-        "base/android/java/src/org/chromium/base/ApplicationStatus.java",
-        "base/android/java/src/org/chromium/base/BaseFeatureList.java",
-        "base/android/java/src/org/chromium/base/BuildInfo.java",
-        "base/android/java/src/org/chromium/base/BundleUtils.java",
-        "base/android/java/src/org/chromium/base/Callback.java",
-        "base/android/java/src/org/chromium/base/CommandLine.java",
-        "base/android/java/src/org/chromium/base/ContentUriUtils.java",
-        "base/android/java/src/org/chromium/base/CpuFeatures.java",
-        "base/android/java/src/org/chromium/base/EarlyTraceEvent.java",
-        "base/android/java/src/org/chromium/base/EventLog.java",
-        "base/android/java/src/org/chromium/base/FeatureList.java",
-        "base/android/java/src/org/chromium/base/Features.java",
-        "base/android/java/src/org/chromium/base/FieldTrialList.java",
-        "base/android/java/src/org/chromium/base/FileUtils.java",
-        "base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java",
-        "base/android/java/src/org/chromium/base/IntStringCallback.java",
-        "base/android/java/src/org/chromium/base/JNIUtils.java",
-        "base/android/java/src/org/chromium/base/JavaExceptionReporter.java",
-        "base/android/java/src/org/chromium/base/JavaHandlerThread.java",
-        "base/android/java/src/org/chromium/base/LocaleUtils.java",
-        "base/android/java/src/org/chromium/base/MemoryPressureListener.java",
-        "base/android/java/src/org/chromium/base/PathService.java",
-        "base/android/java/src/org/chromium/base/PathUtils.java",
-        "base/android/java/src/org/chromium/base/PowerMonitor.java",
-        "base/android/java/src/org/chromium/base/RadioUtils.java",
-        "base/android/java/src/org/chromium/base/SysUtils.java",
-        "base/android/java/src/org/chromium/base/ThreadUtils.java",
-        "base/android/java/src/org/chromium/base/TimezoneUtils.java",
-        "base/android/java/src/org/chromium/base/TraceEvent.java",
-        "base/android/java/src/org/chromium/base/UnguessableToken.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java",
-        "base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
-        "base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java",
-        "base/android/java/src/org/chromium/base/memory/JavaHeapDumpGenerator.java",
-        "base/android/java/src/org/chromium/base/metrics/NativeUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java",
-        "base/android/java/src/org/chromium/base/task/PostTask.java",
-        "base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/base/base_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "ApkAssets_jni.h " +
-         "--output_name " +
-         "ApplicationStatus_jni.h " +
-         "--output_name " +
-         "BaseFeatureList_jni.h " +
-         "--output_name " +
-         "BuildInfo_jni.h " +
-         "--output_name " +
-         "BundleUtils_jni.h " +
-         "--output_name " +
-         "Callback_jni.h " +
-         "--output_name " +
-         "CommandLine_jni.h " +
-         "--output_name " +
-         "ContentUriUtils_jni.h " +
-         "--output_name " +
-         "CpuFeatures_jni.h " +
-         "--output_name " +
-         "EarlyTraceEvent_jni.h " +
-         "--output_name " +
-         "EventLog_jni.h " +
-         "--output_name " +
-         "FeatureList_jni.h " +
-         "--output_name " +
-         "Features_jni.h " +
-         "--output_name " +
-         "FieldTrialList_jni.h " +
-         "--output_name " +
-         "FileUtils_jni.h " +
-         "--output_name " +
-         "ImportantFileWriterAndroid_jni.h " +
-         "--output_name " +
-         "IntStringCallback_jni.h " +
-         "--output_name " +
-         "JNIUtils_jni.h " +
-         "--output_name " +
-         "JavaExceptionReporter_jni.h " +
-         "--output_name " +
-         "JavaHandlerThread_jni.h " +
-         "--output_name " +
-         "LocaleUtils_jni.h " +
-         "--output_name " +
-         "MemoryPressureListener_jni.h " +
-         "--output_name " +
-         "PathService_jni.h " +
-         "--output_name " +
-         "PathUtils_jni.h " +
-         "--output_name " +
-         "PowerMonitor_jni.h " +
-         "--output_name " +
-         "RadioUtils_jni.h " +
-         "--output_name " +
-         "SysUtils_jni.h " +
-         "--output_name " +
-         "ThreadUtils_jni.h " +
-         "--output_name " +
-         "TimezoneUtils_jni.h " +
-         "--output_name " +
-         "TraceEvent_jni.h " +
-         "--output_name " +
-         "UnguessableToken_jni.h " +
-         "--output_name " +
-         "JankMetricUMARecorder_jni.h " +
-         "--output_name " +
-         "LibraryLoader_jni.h " +
-         "--output_name " +
-         "LibraryPrefetcher_jni.h " +
-         "--output_name " +
-         "JavaHeapDumpGenerator_jni.h " +
-         "--output_name " +
-         "NativeUmaRecorder_jni.h " +
-         "--output_name " +
-         "StatisticsRecorderAndroid_jni.h " +
-         "--output_name " +
-         "ChildProcessService_jni.h " +
-         "--output_name " +
-         "PostTask_jni.h " +
-         "--output_name " +
-         "TaskRunnerImpl_jni.h " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ApkAssets.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ApplicationStatus.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/BaseFeatureList.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/BuildInfo.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/BundleUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/Callback.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/CommandLine.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ContentUriUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/CpuFeatures.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/EarlyTraceEvent.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/EventLog.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/FeatureList.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/Features.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/FieldTrialList.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/FileUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/IntStringCallback.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/JNIUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/JavaExceptionReporter.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/JavaHandlerThread.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/LocaleUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/MemoryPressureListener.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/PathService.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/PathUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/PowerMonitor.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/RadioUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/SysUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ThreadUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/TimezoneUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/TraceEvent.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/UnguessableToken.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/memory/JavaHeapDumpGenerator.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/metrics/NativeUmaRecorder.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/task/PostTask.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java)",
-    out: [
-        "base/base_jni_headers/ApkAssets_jni.h",
-        "base/base_jni_headers/ApplicationStatus_jni.h",
-        "base/base_jni_headers/BaseFeatureList_jni.h",
-        "base/base_jni_headers/BuildInfo_jni.h",
-        "base/base_jni_headers/BundleUtils_jni.h",
-        "base/base_jni_headers/Callback_jni.h",
-        "base/base_jni_headers/ChildProcessService_jni.h",
-        "base/base_jni_headers/CommandLine_jni.h",
-        "base/base_jni_headers/ContentUriUtils_jni.h",
-        "base/base_jni_headers/CpuFeatures_jni.h",
-        "base/base_jni_headers/EarlyTraceEvent_jni.h",
-        "base/base_jni_headers/EventLog_jni.h",
-        "base/base_jni_headers/FeatureList_jni.h",
-        "base/base_jni_headers/Features_jni.h",
-        "base/base_jni_headers/FieldTrialList_jni.h",
-        "base/base_jni_headers/FileUtils_jni.h",
-        "base/base_jni_headers/ImportantFileWriterAndroid_jni.h",
-        "base/base_jni_headers/IntStringCallback_jni.h",
-        "base/base_jni_headers/JNIUtils_jni.h",
-        "base/base_jni_headers/JankMetricUMARecorder_jni.h",
-        "base/base_jni_headers/JavaExceptionReporter_jni.h",
-        "base/base_jni_headers/JavaHandlerThread_jni.h",
-        "base/base_jni_headers/JavaHeapDumpGenerator_jni.h",
-        "base/base_jni_headers/LibraryLoader_jni.h",
-        "base/base_jni_headers/LibraryPrefetcher_jni.h",
-        "base/base_jni_headers/LocaleUtils_jni.h",
-        "base/base_jni_headers/MemoryPressureListener_jni.h",
-        "base/base_jni_headers/NativeUmaRecorder_jni.h",
-        "base/base_jni_headers/PathService_jni.h",
-        "base/base_jni_headers/PathUtils_jni.h",
-        "base/base_jni_headers/PostTask_jni.h",
-        "base/base_jni_headers/PowerMonitor_jni.h",
-        "base/base_jni_headers/RadioUtils_jni.h",
-        "base/base_jni_headers/StatisticsRecorderAndroid_jni.h",
-        "base/base_jni_headers/SysUtils_jni.h",
-        "base/base_jni_headers/TaskRunnerImpl_jni.h",
-        "base/base_jni_headers/ThreadUtils_jni.h",
-        "base/base_jni_headers/TimezoneUtils_jni.h",
-        "base/base_jni_headers/TraceEvent_jni.h",
-        "base/base_jni_headers/UnguessableToken_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:base_jni_headers__android_x86
-cc_genrule {
-    name: "cronet_aml_base_base_jni_headers__android_x86",
-    srcs: [
-        "base/android/java/src/org/chromium/base/ApkAssets.java",
-        "base/android/java/src/org/chromium/base/ApplicationStatus.java",
-        "base/android/java/src/org/chromium/base/BaseFeatureList.java",
-        "base/android/java/src/org/chromium/base/BuildInfo.java",
-        "base/android/java/src/org/chromium/base/BundleUtils.java",
-        "base/android/java/src/org/chromium/base/Callback.java",
-        "base/android/java/src/org/chromium/base/CommandLine.java",
-        "base/android/java/src/org/chromium/base/ContentUriUtils.java",
-        "base/android/java/src/org/chromium/base/CpuFeatures.java",
-        "base/android/java/src/org/chromium/base/EarlyTraceEvent.java",
-        "base/android/java/src/org/chromium/base/EventLog.java",
-        "base/android/java/src/org/chromium/base/FeatureList.java",
-        "base/android/java/src/org/chromium/base/Features.java",
-        "base/android/java/src/org/chromium/base/FieldTrialList.java",
-        "base/android/java/src/org/chromium/base/FileUtils.java",
-        "base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java",
-        "base/android/java/src/org/chromium/base/IntStringCallback.java",
-        "base/android/java/src/org/chromium/base/JNIUtils.java",
-        "base/android/java/src/org/chromium/base/JavaExceptionReporter.java",
-        "base/android/java/src/org/chromium/base/JavaHandlerThread.java",
-        "base/android/java/src/org/chromium/base/LocaleUtils.java",
-        "base/android/java/src/org/chromium/base/MemoryPressureListener.java",
-        "base/android/java/src/org/chromium/base/PathService.java",
-        "base/android/java/src/org/chromium/base/PathUtils.java",
-        "base/android/java/src/org/chromium/base/PowerMonitor.java",
-        "base/android/java/src/org/chromium/base/RadioUtils.java",
-        "base/android/java/src/org/chromium/base/SysUtils.java",
-        "base/android/java/src/org/chromium/base/ThreadUtils.java",
-        "base/android/java/src/org/chromium/base/TimezoneUtils.java",
-        "base/android/java/src/org/chromium/base/TraceEvent.java",
-        "base/android/java/src/org/chromium/base/UnguessableToken.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java",
-        "base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
-        "base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java",
-        "base/android/java/src/org/chromium/base/memory/JavaHeapDumpGenerator.java",
-        "base/android/java/src/org/chromium/base/metrics/NativeUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java",
-        "base/android/java/src/org/chromium/base/task/PostTask.java",
-        "base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/base/base_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "ApkAssets_jni.h " +
-         "--output_name " +
-         "ApplicationStatus_jni.h " +
-         "--output_name " +
-         "BaseFeatureList_jni.h " +
-         "--output_name " +
-         "BuildInfo_jni.h " +
-         "--output_name " +
-         "BundleUtils_jni.h " +
-         "--output_name " +
-         "Callback_jni.h " +
-         "--output_name " +
-         "CommandLine_jni.h " +
-         "--output_name " +
-         "ContentUriUtils_jni.h " +
-         "--output_name " +
-         "CpuFeatures_jni.h " +
-         "--output_name " +
-         "EarlyTraceEvent_jni.h " +
-         "--output_name " +
-         "EventLog_jni.h " +
-         "--output_name " +
-         "FeatureList_jni.h " +
-         "--output_name " +
-         "Features_jni.h " +
-         "--output_name " +
-         "FieldTrialList_jni.h " +
-         "--output_name " +
-         "FileUtils_jni.h " +
-         "--output_name " +
-         "ImportantFileWriterAndroid_jni.h " +
-         "--output_name " +
-         "IntStringCallback_jni.h " +
-         "--output_name " +
-         "JNIUtils_jni.h " +
-         "--output_name " +
-         "JavaExceptionReporter_jni.h " +
-         "--output_name " +
-         "JavaHandlerThread_jni.h " +
-         "--output_name " +
-         "LocaleUtils_jni.h " +
-         "--output_name " +
-         "MemoryPressureListener_jni.h " +
-         "--output_name " +
-         "PathService_jni.h " +
-         "--output_name " +
-         "PathUtils_jni.h " +
-         "--output_name " +
-         "PowerMonitor_jni.h " +
-         "--output_name " +
-         "RadioUtils_jni.h " +
-         "--output_name " +
-         "SysUtils_jni.h " +
-         "--output_name " +
-         "ThreadUtils_jni.h " +
-         "--output_name " +
-         "TimezoneUtils_jni.h " +
-         "--output_name " +
-         "TraceEvent_jni.h " +
-         "--output_name " +
-         "UnguessableToken_jni.h " +
-         "--output_name " +
-         "JankMetricUMARecorder_jni.h " +
-         "--output_name " +
-         "LibraryLoader_jni.h " +
-         "--output_name " +
-         "LibraryPrefetcher_jni.h " +
-         "--output_name " +
-         "JavaHeapDumpGenerator_jni.h " +
-         "--output_name " +
-         "NativeUmaRecorder_jni.h " +
-         "--output_name " +
-         "StatisticsRecorderAndroid_jni.h " +
-         "--output_name " +
-         "ChildProcessService_jni.h " +
-         "--output_name " +
-         "PostTask_jni.h " +
-         "--output_name " +
-         "TaskRunnerImpl_jni.h " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ApkAssets.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ApplicationStatus.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/BaseFeatureList.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/BuildInfo.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/BundleUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/Callback.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/CommandLine.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ContentUriUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/CpuFeatures.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/EarlyTraceEvent.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/EventLog.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/FeatureList.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/Features.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/FieldTrialList.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/FileUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/IntStringCallback.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/JNIUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/JavaExceptionReporter.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/JavaHandlerThread.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/LocaleUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/MemoryPressureListener.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/PathService.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/PathUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/PowerMonitor.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/RadioUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/SysUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ThreadUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/TimezoneUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/TraceEvent.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/UnguessableToken.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/memory/JavaHeapDumpGenerator.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/metrics/NativeUmaRecorder.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/task/PostTask.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java)",
-    out: [
-        "base/base_jni_headers/ApkAssets_jni.h",
-        "base/base_jni_headers/ApplicationStatus_jni.h",
-        "base/base_jni_headers/BaseFeatureList_jni.h",
-        "base/base_jni_headers/BuildInfo_jni.h",
-        "base/base_jni_headers/BundleUtils_jni.h",
-        "base/base_jni_headers/Callback_jni.h",
-        "base/base_jni_headers/ChildProcessService_jni.h",
-        "base/base_jni_headers/CommandLine_jni.h",
-        "base/base_jni_headers/ContentUriUtils_jni.h",
-        "base/base_jni_headers/CpuFeatures_jni.h",
-        "base/base_jni_headers/EarlyTraceEvent_jni.h",
-        "base/base_jni_headers/EventLog_jni.h",
-        "base/base_jni_headers/FeatureList_jni.h",
-        "base/base_jni_headers/Features_jni.h",
-        "base/base_jni_headers/FieldTrialList_jni.h",
-        "base/base_jni_headers/FileUtils_jni.h",
-        "base/base_jni_headers/ImportantFileWriterAndroid_jni.h",
-        "base/base_jni_headers/IntStringCallback_jni.h",
-        "base/base_jni_headers/JNIUtils_jni.h",
-        "base/base_jni_headers/JankMetricUMARecorder_jni.h",
-        "base/base_jni_headers/JavaExceptionReporter_jni.h",
-        "base/base_jni_headers/JavaHandlerThread_jni.h",
-        "base/base_jni_headers/JavaHeapDumpGenerator_jni.h",
-        "base/base_jni_headers/LibraryLoader_jni.h",
-        "base/base_jni_headers/LibraryPrefetcher_jni.h",
-        "base/base_jni_headers/LocaleUtils_jni.h",
-        "base/base_jni_headers/MemoryPressureListener_jni.h",
-        "base/base_jni_headers/NativeUmaRecorder_jni.h",
-        "base/base_jni_headers/PathService_jni.h",
-        "base/base_jni_headers/PathUtils_jni.h",
-        "base/base_jni_headers/PostTask_jni.h",
-        "base/base_jni_headers/PowerMonitor_jni.h",
-        "base/base_jni_headers/RadioUtils_jni.h",
-        "base/base_jni_headers/StatisticsRecorderAndroid_jni.h",
-        "base/base_jni_headers/SysUtils_jni.h",
-        "base/base_jni_headers/TaskRunnerImpl_jni.h",
-        "base/base_jni_headers/ThreadUtils_jni.h",
-        "base/base_jni_headers/TimezoneUtils_jni.h",
-        "base/base_jni_headers/TraceEvent_jni.h",
-        "base/base_jni_headers/UnguessableToken_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:base_jni_headers__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_base_jni_headers__android_x86_64",
-    srcs: [
-        "base/android/java/src/org/chromium/base/ApkAssets.java",
-        "base/android/java/src/org/chromium/base/ApplicationStatus.java",
-        "base/android/java/src/org/chromium/base/BaseFeatureList.java",
-        "base/android/java/src/org/chromium/base/BuildInfo.java",
-        "base/android/java/src/org/chromium/base/BundleUtils.java",
-        "base/android/java/src/org/chromium/base/Callback.java",
-        "base/android/java/src/org/chromium/base/CommandLine.java",
-        "base/android/java/src/org/chromium/base/ContentUriUtils.java",
-        "base/android/java/src/org/chromium/base/CpuFeatures.java",
-        "base/android/java/src/org/chromium/base/EarlyTraceEvent.java",
-        "base/android/java/src/org/chromium/base/EventLog.java",
-        "base/android/java/src/org/chromium/base/FeatureList.java",
-        "base/android/java/src/org/chromium/base/Features.java",
-        "base/android/java/src/org/chromium/base/FieldTrialList.java",
-        "base/android/java/src/org/chromium/base/FileUtils.java",
-        "base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java",
-        "base/android/java/src/org/chromium/base/IntStringCallback.java",
-        "base/android/java/src/org/chromium/base/JNIUtils.java",
-        "base/android/java/src/org/chromium/base/JavaExceptionReporter.java",
-        "base/android/java/src/org/chromium/base/JavaHandlerThread.java",
-        "base/android/java/src/org/chromium/base/LocaleUtils.java",
-        "base/android/java/src/org/chromium/base/MemoryPressureListener.java",
-        "base/android/java/src/org/chromium/base/PathService.java",
-        "base/android/java/src/org/chromium/base/PathUtils.java",
-        "base/android/java/src/org/chromium/base/PowerMonitor.java",
-        "base/android/java/src/org/chromium/base/RadioUtils.java",
-        "base/android/java/src/org/chromium/base/SysUtils.java",
-        "base/android/java/src/org/chromium/base/ThreadUtils.java",
-        "base/android/java/src/org/chromium/base/TimezoneUtils.java",
-        "base/android/java/src/org/chromium/base/TraceEvent.java",
-        "base/android/java/src/org/chromium/base/UnguessableToken.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java",
-        "base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
-        "base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java",
-        "base/android/java/src/org/chromium/base/memory/JavaHeapDumpGenerator.java",
-        "base/android/java/src/org/chromium/base/metrics/NativeUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java",
-        "base/android/java/src/org/chromium/base/task/PostTask.java",
-        "base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/base/base_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "ApkAssets_jni.h " +
-         "--output_name " +
-         "ApplicationStatus_jni.h " +
-         "--output_name " +
-         "BaseFeatureList_jni.h " +
-         "--output_name " +
-         "BuildInfo_jni.h " +
-         "--output_name " +
-         "BundleUtils_jni.h " +
-         "--output_name " +
-         "Callback_jni.h " +
-         "--output_name " +
-         "CommandLine_jni.h " +
-         "--output_name " +
-         "ContentUriUtils_jni.h " +
-         "--output_name " +
-         "CpuFeatures_jni.h " +
-         "--output_name " +
-         "EarlyTraceEvent_jni.h " +
-         "--output_name " +
-         "EventLog_jni.h " +
-         "--output_name " +
-         "FeatureList_jni.h " +
-         "--output_name " +
-         "Features_jni.h " +
-         "--output_name " +
-         "FieldTrialList_jni.h " +
-         "--output_name " +
-         "FileUtils_jni.h " +
-         "--output_name " +
-         "ImportantFileWriterAndroid_jni.h " +
-         "--output_name " +
-         "IntStringCallback_jni.h " +
-         "--output_name " +
-         "JNIUtils_jni.h " +
-         "--output_name " +
-         "JavaExceptionReporter_jni.h " +
-         "--output_name " +
-         "JavaHandlerThread_jni.h " +
-         "--output_name " +
-         "LocaleUtils_jni.h " +
-         "--output_name " +
-         "MemoryPressureListener_jni.h " +
-         "--output_name " +
-         "PathService_jni.h " +
-         "--output_name " +
-         "PathUtils_jni.h " +
-         "--output_name " +
-         "PowerMonitor_jni.h " +
-         "--output_name " +
-         "RadioUtils_jni.h " +
-         "--output_name " +
-         "SysUtils_jni.h " +
-         "--output_name " +
-         "ThreadUtils_jni.h " +
-         "--output_name " +
-         "TimezoneUtils_jni.h " +
-         "--output_name " +
-         "TraceEvent_jni.h " +
-         "--output_name " +
-         "UnguessableToken_jni.h " +
-         "--output_name " +
-         "JankMetricUMARecorder_jni.h " +
-         "--output_name " +
-         "LibraryLoader_jni.h " +
-         "--output_name " +
-         "LibraryPrefetcher_jni.h " +
-         "--output_name " +
-         "JavaHeapDumpGenerator_jni.h " +
-         "--output_name " +
-         "NativeUmaRecorder_jni.h " +
-         "--output_name " +
-         "StatisticsRecorderAndroid_jni.h " +
-         "--output_name " +
-         "ChildProcessService_jni.h " +
-         "--output_name " +
-         "PostTask_jni.h " +
-         "--output_name " +
-         "TaskRunnerImpl_jni.h " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ApkAssets.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ApplicationStatus.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/BaseFeatureList.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/BuildInfo.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/BundleUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/Callback.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/CommandLine.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ContentUriUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/CpuFeatures.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/EarlyTraceEvent.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/EventLog.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/FeatureList.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/Features.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/FieldTrialList.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/FileUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/IntStringCallback.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/JNIUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/JavaExceptionReporter.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/JavaHandlerThread.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/LocaleUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/MemoryPressureListener.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/PathService.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/PathUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/PowerMonitor.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/RadioUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/SysUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/ThreadUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/TimezoneUtils.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/TraceEvent.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/UnguessableToken.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/memory/JavaHeapDumpGenerator.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/metrics/NativeUmaRecorder.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/task/PostTask.java) " +
-         "--input_file " +
-         "$(location base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java)",
-    out: [
-        "base/base_jni_headers/ApkAssets_jni.h",
-        "base/base_jni_headers/ApplicationStatus_jni.h",
-        "base/base_jni_headers/BaseFeatureList_jni.h",
-        "base/base_jni_headers/BuildInfo_jni.h",
-        "base/base_jni_headers/BundleUtils_jni.h",
-        "base/base_jni_headers/Callback_jni.h",
-        "base/base_jni_headers/ChildProcessService_jni.h",
-        "base/base_jni_headers/CommandLine_jni.h",
-        "base/base_jni_headers/ContentUriUtils_jni.h",
-        "base/base_jni_headers/CpuFeatures_jni.h",
-        "base/base_jni_headers/EarlyTraceEvent_jni.h",
-        "base/base_jni_headers/EventLog_jni.h",
-        "base/base_jni_headers/FeatureList_jni.h",
-        "base/base_jni_headers/Features_jni.h",
-        "base/base_jni_headers/FieldTrialList_jni.h",
-        "base/base_jni_headers/FileUtils_jni.h",
-        "base/base_jni_headers/ImportantFileWriterAndroid_jni.h",
-        "base/base_jni_headers/IntStringCallback_jni.h",
-        "base/base_jni_headers/JNIUtils_jni.h",
-        "base/base_jni_headers/JankMetricUMARecorder_jni.h",
-        "base/base_jni_headers/JavaExceptionReporter_jni.h",
-        "base/base_jni_headers/JavaHandlerThread_jni.h",
-        "base/base_jni_headers/JavaHeapDumpGenerator_jni.h",
-        "base/base_jni_headers/LibraryLoader_jni.h",
-        "base/base_jni_headers/LibraryPrefetcher_jni.h",
-        "base/base_jni_headers/LocaleUtils_jni.h",
-        "base/base_jni_headers/MemoryPressureListener_jni.h",
-        "base/base_jni_headers/NativeUmaRecorder_jni.h",
-        "base/base_jni_headers/PathService_jni.h",
-        "base/base_jni_headers/PathUtils_jni.h",
-        "base/base_jni_headers/PostTask_jni.h",
-        "base/base_jni_headers/PowerMonitor_jni.h",
-        "base/base_jni_headers/RadioUtils_jni.h",
-        "base/base_jni_headers/StatisticsRecorderAndroid_jni.h",
-        "base/base_jni_headers/SysUtils_jni.h",
-        "base/base_jni_headers/TaskRunnerImpl_jni.h",
-        "base/base_jni_headers/ThreadUtils_jni.h",
-        "base/base_jni_headers/TimezoneUtils_jni.h",
-        "base/base_jni_headers/TraceEvent_jni.h",
-        "base/base_jni_headers/UnguessableToken_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:base_static
-cc_library_static {
-    name: "cronet_aml_base_base_static",
-    srcs: [
-        "base/base_switches.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-            ],
-            export_generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-            ],
-            export_generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-            ],
-            export_generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-            ],
-            export_generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__host",
-            ],
-            export_generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__host",
-            ],
-        },
-    },
-}
-
-// GN: //base:build_date__android_arm
-cc_genrule {
-    name: "cronet_aml_base_build_date__android_arm",
-    cmd: "$(location build/write_build_date_header.py) $(out) " +
-         "1664686800",
-    out: [
-        "base/generated_build_date.h",
-    ],
-    tool_files: [
-        "build/write_build_date_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:build_date__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_build_date__android_arm64",
-    cmd: "$(location build/write_build_date_header.py) $(out) " +
-         "1664686800",
-    out: [
-        "base/generated_build_date.h",
-    ],
-    tool_files: [
-        "build/write_build_date_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:build_date__android_x86
-cc_genrule {
-    name: "cronet_aml_base_build_date__android_x86",
-    cmd: "$(location build/write_build_date_header.py) $(out) " +
-         "1664686800",
-    out: [
-        "base/generated_build_date.h",
-    ],
-    tool_files: [
-        "build/write_build_date_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:build_date__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_build_date__android_x86_64",
-    cmd: "$(location build/write_build_date_header.py) $(out) " +
-         "1664686800",
-    out: [
-        "base/generated_build_date.h",
-    ],
-    tool_files: [
-        "build/write_build_date_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:build_date__host
-cc_genrule {
-    name: "cronet_aml_base_build_date__host",
-    cmd: "$(location build/write_build_date_header.py) $(out) " +
-         "1664686800",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/generated_build_date.h",
-    ],
-    tool_files: [
-        "build/write_build_date_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:cfi_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_cfi_buildflags__android_arm",
-    cmd: "echo '--flags CFI_CAST_CHECK=\"false && false\" CFI_DIAG=\"false && false\" CFI_ICALL_CHECK=\"false && false\" CFI_ENFORCEMENT_TRAP=\"false && !false\" CFI_ENFORCEMENT_DIAGNOSTIC=\"false && false && !false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:cfi_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/cfi_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:cfi_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_cfi_buildflags__android_arm64",
-    cmd: "echo '--flags CFI_CAST_CHECK=\"false && false\" CFI_DIAG=\"false && false\" CFI_ICALL_CHECK=\"false && false\" CFI_ENFORCEMENT_TRAP=\"false && !false\" CFI_ENFORCEMENT_DIAGNOSTIC=\"false && false && !false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:cfi_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/cfi_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:cfi_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_cfi_buildflags__android_x86",
-    cmd: "echo '--flags CFI_CAST_CHECK=\"false && false\" CFI_DIAG=\"false && false\" CFI_ICALL_CHECK=\"false && false\" CFI_ENFORCEMENT_TRAP=\"false && !false\" CFI_ENFORCEMENT_DIAGNOSTIC=\"false && false && !false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:cfi_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/cfi_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:cfi_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_cfi_buildflags__android_x86_64",
-    cmd: "echo '--flags CFI_CAST_CHECK=\"false && false\" CFI_DIAG=\"false && false\" CFI_ICALL_CHECK=\"false && false\" CFI_ENFORCEMENT_TRAP=\"false && !false\" CFI_ENFORCEMENT_DIAGNOSTIC=\"false && false && !false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:cfi_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/cfi_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:cfi_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_cfi_buildflags__host",
-    cmd: "echo '--flags CFI_CAST_CHECK=\"false && false\" CFI_DIAG=\"false && false\" CFI_ICALL_CHECK=\"false && false\" CFI_ENFORCEMENT_TRAP=\"false && !false\" CFI_ENFORCEMENT_DIAGNOSTIC=\"false && false && !false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:cfi_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/cfi_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:clang_profiling_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_clang_profiling_buildflags__android_arm",
-    cmd: "echo '--flags CLANG_PROFILING=\"false\" CLANG_PROFILING_INSIDE_SANDBOX=\"false\" USE_CLANG_COVERAGE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:clang_profiling_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/clang_profiling_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:clang_profiling_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_clang_profiling_buildflags__android_arm64",
-    cmd: "echo '--flags CLANG_PROFILING=\"false\" CLANG_PROFILING_INSIDE_SANDBOX=\"false\" USE_CLANG_COVERAGE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:clang_profiling_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/clang_profiling_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:clang_profiling_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_clang_profiling_buildflags__android_x86",
-    cmd: "echo '--flags CLANG_PROFILING=\"false\" CLANG_PROFILING_INSIDE_SANDBOX=\"false\" USE_CLANG_COVERAGE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:clang_profiling_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/clang_profiling_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:clang_profiling_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_clang_profiling_buildflags__android_x86_64",
-    cmd: "echo '--flags CLANG_PROFILING=\"false\" CLANG_PROFILING_INSIDE_SANDBOX=\"false\" USE_CLANG_COVERAGE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:clang_profiling_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/clang_profiling_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:clang_profiling_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_clang_profiling_buildflags__host",
-    cmd: "echo '--flags CLANG_PROFILING=\"false\" CLANG_PROFILING_INSIDE_SANDBOX=\"false\" USE_CLANG_COVERAGE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:clang_profiling_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/clang_profiling_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:debugging_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_debugging_buildflags__android_arm",
-    cmd: "echo '--flags DCHECK_IS_CONFIGURABLE=\"false\" ENABLE_LOCATION_SOURCE=\"true\" ENABLE_PROFILING=\"false\" CAN_UNWIND_WITH_FRAME_POINTERS=\"false\" UNSAFE_DEVELOPER_BUILD=\"true\" CAN_UNWIND_WITH_CFI_TABLE=\"true\" EXCLUDE_UNWIND_TABLES=\"false\" ENABLE_GDBINIT_WARNING=\"true\" ENABLE_LLDBINIT_WARNING=\"false\" EXPENSIVE_DCHECKS_ARE_ON=\"true\" ENABLE_STACK_TRACE_LINE_NUMBERS=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:debugging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/debug/debugging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:debugging_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_debugging_buildflags__android_arm64",
-    cmd: "echo '--flags DCHECK_IS_CONFIGURABLE=\"false\" ENABLE_LOCATION_SOURCE=\"true\" ENABLE_PROFILING=\"false\" CAN_UNWIND_WITH_FRAME_POINTERS=\"true\" UNSAFE_DEVELOPER_BUILD=\"true\" CAN_UNWIND_WITH_CFI_TABLE=\"false\" EXCLUDE_UNWIND_TABLES=\"false\" ENABLE_GDBINIT_WARNING=\"true\" ENABLE_LLDBINIT_WARNING=\"false\" EXPENSIVE_DCHECKS_ARE_ON=\"true\" ENABLE_STACK_TRACE_LINE_NUMBERS=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:debugging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/debug/debugging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:debugging_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_debugging_buildflags__android_x86",
-    cmd: "echo '--flags DCHECK_IS_CONFIGURABLE=\"false\" ENABLE_LOCATION_SOURCE=\"true\" ENABLE_PROFILING=\"false\" CAN_UNWIND_WITH_FRAME_POINTERS=\"true\" UNSAFE_DEVELOPER_BUILD=\"true\" CAN_UNWIND_WITH_CFI_TABLE=\"false\" EXCLUDE_UNWIND_TABLES=\"false\" ENABLE_GDBINIT_WARNING=\"true\" ENABLE_LLDBINIT_WARNING=\"false\" EXPENSIVE_DCHECKS_ARE_ON=\"true\" ENABLE_STACK_TRACE_LINE_NUMBERS=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:debugging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/debug/debugging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:debugging_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_debugging_buildflags__android_x86_64",
-    cmd: "echo '--flags DCHECK_IS_CONFIGURABLE=\"false\" ENABLE_LOCATION_SOURCE=\"true\" ENABLE_PROFILING=\"false\" CAN_UNWIND_WITH_FRAME_POINTERS=\"false\" UNSAFE_DEVELOPER_BUILD=\"true\" CAN_UNWIND_WITH_CFI_TABLE=\"false\" EXCLUDE_UNWIND_TABLES=\"false\" ENABLE_GDBINIT_WARNING=\"true\" ENABLE_LLDBINIT_WARNING=\"false\" EXPENSIVE_DCHECKS_ARE_ON=\"true\" ENABLE_STACK_TRACE_LINE_NUMBERS=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:debugging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/debug/debugging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:debugging_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_debugging_buildflags__host",
-    cmd: "echo '--flags DCHECK_IS_CONFIGURABLE=\"false\" ENABLE_LOCATION_SOURCE=\"true\" ENABLE_PROFILING=\"false\" CAN_UNWIND_WITH_FRAME_POINTERS=\"true\" UNSAFE_DEVELOPER_BUILD=\"true\" CAN_UNWIND_WITH_CFI_TABLE=\"false\" EXCLUDE_UNWIND_TABLES=\"false\" ENABLE_GDBINIT_WARNING=\"true\" ENABLE_LLDBINIT_WARNING=\"false\" EXPENSIVE_DCHECKS_ARE_ON=\"true\" ENABLE_STACK_TRACE_LINE_NUMBERS=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:debugging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/debug/debugging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:feature_list_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_feature_list_buildflags__android_arm",
-    cmd: "echo '--flags ENABLE_BANNED_BASE_FEATURE_PREFIX=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:feature_list_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/feature_list_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:feature_list_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_feature_list_buildflags__android_arm64",
-    cmd: "echo '--flags ENABLE_BANNED_BASE_FEATURE_PREFIX=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:feature_list_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/feature_list_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:feature_list_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_feature_list_buildflags__android_x86",
-    cmd: "echo '--flags ENABLE_BANNED_BASE_FEATURE_PREFIX=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:feature_list_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/feature_list_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:feature_list_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_feature_list_buildflags__android_x86_64",
-    cmd: "echo '--flags ENABLE_BANNED_BASE_FEATURE_PREFIX=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:feature_list_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/feature_list_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:feature_list_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_feature_list_buildflags__host",
-    cmd: "echo '--flags ENABLE_BANNED_BASE_FEATURE_PREFIX=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:feature_list_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/feature_list_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:ios_cronet_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_ios_cronet_buildflags__android_arm",
-    cmd: "echo '--flags CRONET_BUILD=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:ios_cronet_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/message_loop/ios_cronet_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:ios_cronet_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_ios_cronet_buildflags__android_arm64",
-    cmd: "echo '--flags CRONET_BUILD=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:ios_cronet_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/message_loop/ios_cronet_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:ios_cronet_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_ios_cronet_buildflags__android_x86",
-    cmd: "echo '--flags CRONET_BUILD=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:ios_cronet_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/message_loop/ios_cronet_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:ios_cronet_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_ios_cronet_buildflags__android_x86_64",
-    cmd: "echo '--flags CRONET_BUILD=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:ios_cronet_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/message_loop/ios_cronet_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:ios_cronet_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_ios_cronet_buildflags__host",
-    cmd: "echo '--flags CRONET_BUILD=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:ios_cronet_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/message_loop/ios_cronet_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:java_features_srcjar
-java_genrule {
-    name: "cronet_aml_base_java_features_srcjar",
-    srcs: [
-        "base/android/base_features.cc",
-        "base/features.cc",
-        "base/task/task_features.cc",
-    ],
-    cmd: "$(location build/android/gyp/java_cpp_features.py) --srcjar " +
-         "$(out) " +
-         "--template " +
-         "$(location base/android/java/src/org/chromium/base/BaseFeatures.java.tmpl) " +
-         "$(location base/android/base_features.cc) " +
-         "$(location base/features.cc) " +
-         "$(location base/task/task_features.cc)",
-    out: [
-        "base/java_features_srcjar.srcjar",
-    ],
-    tool_files: [
-        "base/android/java/src/org/chromium/base/BaseFeatures.java.tmpl",
-        "build/android/gyp/java_cpp_features.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/android/gyp/util/java_cpp_utils.py",
-        "build/gn_helpers.py",
-    ],
-}
-
-// GN: //base:java_switches_srcjar
-java_genrule {
-    name: "cronet_aml_base_java_switches_srcjar",
-    srcs: [
-        "base/base_switches.cc",
-    ],
-    cmd: "$(location build/android/gyp/java_cpp_strings.py) --srcjar " +
-         "$(out) " +
-         "--template " +
-         "$(location base/android/java/src/org/chromium/base/BaseSwitches.java.tmpl) " +
-         "$(location base/base_switches.cc)",
-    out: [
-        "base/java_switches_srcjar.srcjar",
-    ],
-    tool_files: [
-        "base/android/java/src/org/chromium/base/BaseSwitches.java.tmpl",
-        "build/android/gyp/java_cpp_strings.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/android/gyp/util/java_cpp_utils.py",
-        "build/gn_helpers.py",
-    ],
-}
-
-// GN: //base:logging_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_logging_buildflags__android_arm",
-    cmd: "echo '--flags ENABLE_LOG_ERROR_NOT_REACHED=\"false\" USE_RUNTIME_VLOG=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:logging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/logging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:logging_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_logging_buildflags__android_arm64",
-    cmd: "echo '--flags ENABLE_LOG_ERROR_NOT_REACHED=\"false\" USE_RUNTIME_VLOG=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:logging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/logging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:logging_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_logging_buildflags__android_x86",
-    cmd: "echo '--flags ENABLE_LOG_ERROR_NOT_REACHED=\"false\" USE_RUNTIME_VLOG=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:logging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/logging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:logging_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_logging_buildflags__android_x86_64",
-    cmd: "echo '--flags ENABLE_LOG_ERROR_NOT_REACHED=\"false\" USE_RUNTIME_VLOG=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:logging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/logging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:logging_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_logging_buildflags__host",
-    cmd: "echo '--flags ENABLE_LOG_ERROR_NOT_REACHED=\"false\" USE_RUNTIME_VLOG=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:logging_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/logging_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:message_pump_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_message_pump_buildflags__android_arm",
-    cmd: "echo '--flags ENABLE_MESSAGE_PUMP_EPOLL=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:message_pump_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/message_loop/message_pump_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:message_pump_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_message_pump_buildflags__android_arm64",
-    cmd: "echo '--flags ENABLE_MESSAGE_PUMP_EPOLL=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:message_pump_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/message_loop/message_pump_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:message_pump_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_message_pump_buildflags__android_x86",
-    cmd: "echo '--flags ENABLE_MESSAGE_PUMP_EPOLL=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:message_pump_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/message_loop/message_pump_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:message_pump_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_message_pump_buildflags__android_x86_64",
-    cmd: "echo '--flags ENABLE_MESSAGE_PUMP_EPOLL=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:message_pump_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/message_loop/message_pump_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:message_pump_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_message_pump_buildflags__host",
-    cmd: "echo '--flags ENABLE_MESSAGE_PUMP_EPOLL=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:message_pump_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/message_loop/message_pump_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/numerics:base_numerics
-cc_object {
-    name: "cronet_aml_base_numerics_base_numerics",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //base:orderfile_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_orderfile_buildflags__android_arm",
-    cmd: "echo '--flags DEVTOOLS_INSTRUMENTATION_DUMPING=\"false\" ORDERFILE_INSTRUMENTATION=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:orderfile_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/android/orderfile/orderfile_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:orderfile_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_orderfile_buildflags__android_arm64",
-    cmd: "echo '--flags DEVTOOLS_INSTRUMENTATION_DUMPING=\"false\" ORDERFILE_INSTRUMENTATION=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:orderfile_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/android/orderfile/orderfile_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:orderfile_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_orderfile_buildflags__android_x86",
-    cmd: "echo '--flags DEVTOOLS_INSTRUMENTATION_DUMPING=\"false\" ORDERFILE_INSTRUMENTATION=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:orderfile_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/android/orderfile/orderfile_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:orderfile_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_orderfile_buildflags__android_x86_64",
-    cmd: "echo '--flags DEVTOOLS_INSTRUMENTATION_DUMPING=\"false\" ORDERFILE_INSTRUMENTATION=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:orderfile_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/android/orderfile/orderfile_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:orderfile_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_orderfile_buildflags__host",
-    cmd: "echo '--flags DEVTOOLS_INSTRUMENTATION_DUMPING=\"false\" ORDERFILE_INSTRUMENTATION=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:orderfile_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/android/orderfile/orderfile_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:parsing_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_parsing_buildflags__android_arm",
-    cmd: "echo '--flags BUILD_RUST_JSON_PARSER=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:parsing_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/parsing_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:parsing_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_parsing_buildflags__android_arm64",
-    cmd: "echo '--flags BUILD_RUST_JSON_PARSER=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:parsing_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/parsing_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:parsing_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_parsing_buildflags__android_x86",
-    cmd: "echo '--flags BUILD_RUST_JSON_PARSER=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:parsing_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/parsing_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:parsing_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_parsing_buildflags__android_x86_64",
-    cmd: "echo '--flags BUILD_RUST_JSON_PARSER=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:parsing_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/parsing_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:parsing_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_parsing_buildflags__host",
-    cmd: "echo '--flags BUILD_RUST_JSON_PARSER=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:parsing_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/parsing_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:power_monitor_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_power_monitor_buildflags__android_arm",
-    cmd: "echo '--flags HAS_BATTERY_LEVEL_PROVIDER_IMPL=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:power_monitor_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/power_monitor/power_monitor_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:power_monitor_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_power_monitor_buildflags__android_arm64",
-    cmd: "echo '--flags HAS_BATTERY_LEVEL_PROVIDER_IMPL=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:power_monitor_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/power_monitor/power_monitor_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:power_monitor_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_power_monitor_buildflags__android_x86",
-    cmd: "echo '--flags HAS_BATTERY_LEVEL_PROVIDER_IMPL=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:power_monitor_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/power_monitor/power_monitor_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:power_monitor_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_power_monitor_buildflags__android_x86_64",
-    cmd: "echo '--flags HAS_BATTERY_LEVEL_PROVIDER_IMPL=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:power_monitor_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/power_monitor/power_monitor_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:power_monitor_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_power_monitor_buildflags__host",
-    cmd: "echo '--flags HAS_BATTERY_LEVEL_PROVIDER_IMPL=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:power_monitor_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/power_monitor/power_monitor_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:profiler_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_profiler_buildflags__android_arm",
-    cmd: "echo '--flags ENABLE_ARM_CFI_TABLE=\"true\" IOS_STACK_PROFILER_ENABLED=\"true\" USE_ANDROID_UNWINDER_V2=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:profiler_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/profiler/profiler_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:profiler_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_profiler_buildflags__android_arm64",
-    cmd: "echo '--flags ENABLE_ARM_CFI_TABLE=\"false\" IOS_STACK_PROFILER_ENABLED=\"true\" USE_ANDROID_UNWINDER_V2=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:profiler_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/profiler/profiler_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:profiler_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_profiler_buildflags__android_x86",
-    cmd: "echo '--flags ENABLE_ARM_CFI_TABLE=\"false\" IOS_STACK_PROFILER_ENABLED=\"true\" USE_ANDROID_UNWINDER_V2=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:profiler_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/profiler/profiler_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:profiler_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_profiler_buildflags__android_x86_64",
-    cmd: "echo '--flags ENABLE_ARM_CFI_TABLE=\"false\" IOS_STACK_PROFILER_ENABLED=\"true\" USE_ANDROID_UNWINDER_V2=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:profiler_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/profiler/profiler_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:profiler_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_profiler_buildflags__host",
-    cmd: "echo '--flags ENABLE_ARM_CFI_TABLE=\"false\" IOS_STACK_PROFILER_ENABLED=\"true\" USE_ANDROID_UNWINDER_V2=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:profiler_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/profiler/profiler_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:sanitizer_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_sanitizer_buildflags__android_arm",
-    cmd: "echo '--flags IS_HWASAN=\"false\" USING_SANITIZER=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:sanitizer_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/sanitizer_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:sanitizer_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_sanitizer_buildflags__android_arm64",
-    cmd: "echo '--flags IS_HWASAN=\"false\" USING_SANITIZER=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:sanitizer_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/sanitizer_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:sanitizer_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_sanitizer_buildflags__android_x86",
-    cmd: "echo '--flags IS_HWASAN=\"false\" USING_SANITIZER=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:sanitizer_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/sanitizer_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:sanitizer_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_sanitizer_buildflags__android_x86_64",
-    cmd: "echo '--flags IS_HWASAN=\"false\" USING_SANITIZER=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:sanitizer_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/sanitizer_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:sanitizer_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_sanitizer_buildflags__host",
-    cmd: "echo '--flags IS_HWASAN=\"false\" USING_SANITIZER=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:sanitizer_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/sanitizer_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:synchronization_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_synchronization_buildflags__android_arm",
-    cmd: "echo '--flags ENABLE_MUTEX_PRIORITY_INHERITANCE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:synchronization_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/synchronization/synchronization_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:synchronization_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_synchronization_buildflags__android_arm64",
-    cmd: "echo '--flags ENABLE_MUTEX_PRIORITY_INHERITANCE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:synchronization_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/synchronization/synchronization_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:synchronization_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_synchronization_buildflags__android_x86",
-    cmd: "echo '--flags ENABLE_MUTEX_PRIORITY_INHERITANCE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:synchronization_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/synchronization/synchronization_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:synchronization_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_synchronization_buildflags__android_x86_64",
-    cmd: "echo '--flags ENABLE_MUTEX_PRIORITY_INHERITANCE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:synchronization_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/synchronization/synchronization_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:synchronization_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_synchronization_buildflags__host",
-    cmd: "echo '--flags ENABLE_MUTEX_PRIORITY_INHERITANCE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:synchronization_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/synchronization/synchronization_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base/third_party/double_conversion:double_conversion
-cc_library_static {
-    name: "cronet_aml_base_third_party_double_conversion_double_conversion",
-    srcs: [
-        "base/third_party/double_conversion/double-conversion/bignum-dtoa.cc",
-        "base/third_party/double_conversion/double-conversion/bignum.cc",
-        "base/third_party/double_conversion/double-conversion/cached-powers.cc",
-        "base/third_party/double_conversion/double-conversion/double-to-string.cc",
-        "base/third_party/double_conversion/double-conversion/fast-dtoa.cc",
-        "base/third_party/double_conversion/double-conversion/fixed-dtoa.cc",
-        "base/third_party/double_conversion/double-conversion/string-to-double.cc",
-        "base/third_party/double_conversion/double-conversion/strtod.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //base/third_party/dynamic_annotations:dynamic_annotations
-cc_library_static {
-    name: "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-    srcs: [
-        "base/third_party/dynamic_annotations/dynamic_annotations.c",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //base/third_party/symbolize:symbolize
-cc_library_static {
-    name: "cronet_aml_base_third_party_symbolize_symbolize",
-    srcs: [
-        "base/third_party/symbolize/demangle.cc",
-        "base/third_party/symbolize/symbolize.cc",
-    ],
-    host_supported: true,
-    device_supported: false,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DGLOG_EXPORT=",
-        "-DUSE_AURA=1",
-        "-DUSE_OZONE=1",
-        "-DUSE_UDEV",
-        "-D_DEBUG",
-        "-D_FILE_OFFSET_BITS=64",
-        "-D_GNU_SOURCE",
-        "-D_LARGEFILE64_SOURCE",
-        "-D_LARGEFILE_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-msse3",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-}
-
-// GN: //base/third_party/xdg_mime:xdg_mime
-cc_library_static {
-    name: "cronet_aml_base_third_party_xdg_mime_xdg_mime",
-    srcs: [
-        "base/third_party/xdg_mime/xdgmime.c",
-        "base/third_party/xdg_mime/xdgmimealias.c",
-        "base/third_party/xdg_mime/xdgmimecache.c",
-        "base/third_party/xdg_mime/xdgmimeglob.c",
-        "base/third_party/xdg_mime/xdgmimeicon.c",
-        "base/third_party/xdg_mime/xdgmimeint.c",
-        "base/third_party/xdg_mime/xdgmimemagic.c",
-        "base/third_party/xdg_mime/xdgmimeparent.c",
-    ],
-    host_supported: true,
-    device_supported: false,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DUSE_AURA=1",
-        "-DUSE_OZONE=1",
-        "-DUSE_UDEV",
-        "-D_DEBUG",
-        "-D_FILE_OFFSET_BITS=64",
-        "-D_GNU_SOURCE",
-        "-D_LARGEFILE64_SOURCE",
-        "-D_LARGEFILE_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-msse3",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-}
-
-// GN: //base/third_party/xdg_user_dirs:xdg_user_dirs
-cc_library_static {
-    name: "cronet_aml_base_third_party_xdg_user_dirs_xdg_user_dirs",
-    srcs: [
-        "base/third_party/xdg_user_dirs/xdg_user_dir_lookup.cc",
-    ],
-    host_supported: true,
-    device_supported: false,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DUSE_AURA=1",
-        "-DUSE_OZONE=1",
-        "-DUSE_UDEV",
-        "-D_DEBUG",
-        "-D_FILE_OFFSET_BITS=64",
-        "-D_GNU_SOURCE",
-        "-D_LARGEFILE64_SOURCE",
-        "-D_LARGEFILE_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-        "-msse3",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-}
-
-// GN: //base:tracing_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_base_tracing_buildflags__android_arm",
-    cmd: "echo '--flags ENABLE_BASE_TRACING=\"false\" USE_PERFETTO_CLIENT_LIBRARY=\"false\" OPTIONAL_TRACE_EVENTS_ENABLED=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:tracing_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/tracing_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:tracing_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_base_tracing_buildflags__android_arm64",
-    cmd: "echo '--flags ENABLE_BASE_TRACING=\"false\" USE_PERFETTO_CLIENT_LIBRARY=\"false\" OPTIONAL_TRACE_EVENTS_ENABLED=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:tracing_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/tracing_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:tracing_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_base_tracing_buildflags__android_x86",
-    cmd: "echo '--flags ENABLE_BASE_TRACING=\"false\" USE_PERFETTO_CLIENT_LIBRARY=\"false\" OPTIONAL_TRACE_EVENTS_ENABLED=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:tracing_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/tracing_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:tracing_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_base_tracing_buildflags__android_x86_64",
-    cmd: "echo '--flags ENABLE_BASE_TRACING=\"false\" USE_PERFETTO_CLIENT_LIBRARY=\"false\" OPTIONAL_TRACE_EVENTS_ENABLED=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:tracing_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "base/tracing_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //base:tracing_buildflags__host
-cc_genrule {
-    name: "cronet_aml_base_tracing_buildflags__host",
-    cmd: "echo '--flags ENABLE_BASE_TRACING=\"false\" USE_PERFETTO_CLIENT_LIBRARY=\"false\" OPTIONAL_TRACE_EVENTS_ENABLED=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//base:tracing_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "base/tracing_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build/android:build_config_gen
-genrule {
-    name: "cronet_aml_build_android_build_config_gen",
-    srcs: [
-        ":cronet_aml_build_android_build_config_gen_preprocess",
-    ],
-    tools: [
-        "soong_zip",
-    ],
-    cmd: "cp $(in) $(genDir)/BuildConfig.java && " +
-         "$(location soong_zip) -o $(out) -srcjar -f $(genDir)/BuildConfig.java",
-    out: [
-        "BuildConfig.srcjar",
-    ],
-}
-
-// GN: //build/android:build_config_gen
-cc_object {
-    name: "cronet_aml_build_android_build_config_gen_preprocess",
-    srcs: [
-        ":cronet_aml_build_android_build_config_gen_rename",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-D_ENABLE_ASSERTS",
-        "-E",
-        "-P",
-    ],
-    compile_multilib: "first",
-}
-
-// GN: //build/android:build_config_gen
-genrule {
-    name: "cronet_aml_build_android_build_config_gen_rename",
-    srcs: [
-        "build/android/java/templates/BuildConfig.template",
-    ],
-    cmd: "cp $(in) $(out)",
-    out: [
-        "BuildConfig.cc",
-    ],
-}
-
-// GN: //build/android:native_libraries_gen
-java_genrule {
-    name: "cronet_aml_build_android_native_libraries_gen",
-    cmd: "$(location build/android/gyp/write_native_libraries_java.py) --output " +
-         "$(out) " +
-         "--cpu-family " +
-         "CPU_FAMILY_ARM",
-    out: [
-        "build/android/native_libraries_gen.srcjar",
-    ],
-    tool_files: [
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/android/gyp/write_native_libraries_java.py",
-        "build/gn_helpers.py",
-    ],
-}
-
-// GN: //build:branding_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_build_branding_buildflags__android_arm",
-    cmd: "echo '--flags CHROMIUM_BRANDING=\"1\" GOOGLE_CHROME_BRANDING=\"0\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build:branding_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "build/branding_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build:branding_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_build_branding_buildflags__android_arm64",
-    cmd: "echo '--flags CHROMIUM_BRANDING=\"1\" GOOGLE_CHROME_BRANDING=\"0\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build:branding_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "build/branding_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build:branding_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_build_branding_buildflags__android_x86",
-    cmd: "echo '--flags CHROMIUM_BRANDING=\"1\" GOOGLE_CHROME_BRANDING=\"0\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build:branding_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "build/branding_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build:branding_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_build_branding_buildflags__android_x86_64",
-    cmd: "echo '--flags CHROMIUM_BRANDING=\"1\" GOOGLE_CHROME_BRANDING=\"0\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build:branding_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "build/branding_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build:branding_buildflags__host
-cc_genrule {
-    name: "cronet_aml_build_branding_buildflags__host",
-    cmd: "echo '--flags CHROMIUM_BRANDING=\"1\" GOOGLE_CHROME_BRANDING=\"0\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build:branding_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "build/branding_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build:buildflag_header_h
-cc_object {
-    name: "cronet_aml_build_buildflag_header_h",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //build:chromecast_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_build_chromecast_buildflags__android_arm",
-    cmd: "echo '--flags IS_CASTOS=\"false\" IS_CAST_ANDROID=\"false\" ENABLE_CAST_RECEIVER=\"false\" IS_CHROMECAST=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build:chromecast_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "build/chromecast_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build:chromecast_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_build_chromecast_buildflags__android_arm64",
-    cmd: "echo '--flags IS_CASTOS=\"false\" IS_CAST_ANDROID=\"false\" ENABLE_CAST_RECEIVER=\"false\" IS_CHROMECAST=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build:chromecast_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "build/chromecast_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build:chromecast_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_build_chromecast_buildflags__android_x86",
-    cmd: "echo '--flags IS_CASTOS=\"false\" IS_CAST_ANDROID=\"false\" ENABLE_CAST_RECEIVER=\"false\" IS_CHROMECAST=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build:chromecast_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "build/chromecast_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build:chromecast_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_build_chromecast_buildflags__android_x86_64",
-    cmd: "echo '--flags IS_CASTOS=\"false\" IS_CAST_ANDROID=\"false\" ENABLE_CAST_RECEIVER=\"false\" IS_CHROMECAST=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build:chromecast_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "build/chromecast_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build:chromecast_buildflags__host
-cc_genrule {
-    name: "cronet_aml_build_chromecast_buildflags__host",
-    cmd: "echo '--flags IS_CASTOS=\"false\" IS_CAST_ANDROID=\"false\" ENABLE_CAST_RECEIVER=\"false\" IS_CHROMECAST=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build:chromecast_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "build/chromecast_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build:chromeos_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_build_chromeos_buildflags__android_arm",
-    cmd: "echo '--flags IS_CHROMEOS_DEVICE=\"false\" IS_CHROMEOS_LACROS=\"false\" IS_CHROMEOS_ASH=\"false\" IS_CHROMEOS_WITH_HW_DETAILS=\"false\" IS_REVEN=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build:chromeos_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "build/chromeos_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build:chromeos_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_build_chromeos_buildflags__android_arm64",
-    cmd: "echo '--flags IS_CHROMEOS_DEVICE=\"false\" IS_CHROMEOS_LACROS=\"false\" IS_CHROMEOS_ASH=\"false\" IS_CHROMEOS_WITH_HW_DETAILS=\"false\" IS_REVEN=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build:chromeos_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "build/chromeos_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build:chromeos_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_build_chromeos_buildflags__android_x86",
-    cmd: "echo '--flags IS_CHROMEOS_DEVICE=\"false\" IS_CHROMEOS_LACROS=\"false\" IS_CHROMEOS_ASH=\"false\" IS_CHROMEOS_WITH_HW_DETAILS=\"false\" IS_REVEN=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build:chromeos_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "build/chromeos_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build:chromeos_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_build_chromeos_buildflags__android_x86_64",
-    cmd: "echo '--flags IS_CHROMEOS_DEVICE=\"false\" IS_CHROMEOS_LACROS=\"false\" IS_CHROMEOS_ASH=\"false\" IS_CHROMEOS_WITH_HW_DETAILS=\"false\" IS_REVEN=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build:chromeos_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "build/chromeos_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build:chromeos_buildflags__host
-cc_genrule {
-    name: "cronet_aml_build_chromeos_buildflags__host",
-    cmd: "echo '--flags IS_CHROMEOS_DEVICE=\"false\" IS_CHROMEOS_LACROS=\"false\" IS_CHROMEOS_ASH=\"false\" IS_CHROMEOS_WITH_HW_DETAILS=\"false\" IS_REVEN=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build:chromeos_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "build/chromeos_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build/config/compiler:compiler_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_build_config_compiler_compiler_buildflags__android_arm",
-    cmd: "echo '--flags CLANG_PGO=\"0\" SYMBOL_LEVEL=\"1\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build/config/compiler:compiler_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "build/config/compiler/compiler_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build/config/compiler:compiler_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_build_config_compiler_compiler_buildflags__android_arm64",
-    cmd: "echo '--flags CLANG_PGO=\"0\" SYMBOL_LEVEL=\"1\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build/config/compiler:compiler_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "build/config/compiler/compiler_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build/config/compiler:compiler_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_build_config_compiler_compiler_buildflags__android_x86",
-    cmd: "echo '--flags CLANG_PGO=\"0\" SYMBOL_LEVEL=\"1\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build/config/compiler:compiler_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "build/config/compiler/compiler_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build/config/compiler:compiler_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_build_config_compiler_compiler_buildflags__android_x86_64",
-    cmd: "echo '--flags CLANG_PGO=\"0\" SYMBOL_LEVEL=\"1\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build/config/compiler:compiler_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "build/config/compiler/compiler_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //build/config/compiler:compiler_buildflags__host
-cc_genrule {
-    name: "cronet_aml_build_config_compiler_compiler_buildflags__host",
-    cmd: "echo '--flags CLANG_PGO=\"0\" SYMBOL_LEVEL=\"2\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//build/config/compiler:compiler_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "build/config/compiler/compiler_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //buildtools/third_party/libc++:libc++
-cc_object {
-    name: "cronet_aml_buildtools_third_party_libc___libc__",
-    srcs: [
-        "buildtools/third_party/libc++/trunk/src/algorithm.cpp",
-        "buildtools/third_party/libc++/trunk/src/any.cpp",
-        "buildtools/third_party/libc++/trunk/src/atomic.cpp",
-        "buildtools/third_party/libc++/trunk/src/barrier.cpp",
-        "buildtools/third_party/libc++/trunk/src/bind.cpp",
-        "buildtools/third_party/libc++/trunk/src/charconv.cpp",
-        "buildtools/third_party/libc++/trunk/src/chrono.cpp",
-        "buildtools/third_party/libc++/trunk/src/condition_variable.cpp",
-        "buildtools/third_party/libc++/trunk/src/condition_variable_destructor.cpp",
-        "buildtools/third_party/libc++/trunk/src/exception.cpp",
-        "buildtools/third_party/libc++/trunk/src/format.cpp",
-        "buildtools/third_party/libc++/trunk/src/functional.cpp",
-        "buildtools/third_party/libc++/trunk/src/future.cpp",
-        "buildtools/third_party/libc++/trunk/src/hash.cpp",
-        "buildtools/third_party/libc++/trunk/src/ios.cpp",
-        "buildtools/third_party/libc++/trunk/src/ios.instantiations.cpp",
-        "buildtools/third_party/libc++/trunk/src/iostream.cpp",
-        "buildtools/third_party/libc++/trunk/src/legacy_pointer_safety.cpp",
-        "buildtools/third_party/libc++/trunk/src/locale.cpp",
-        "buildtools/third_party/libc++/trunk/src/memory.cpp",
-        "buildtools/third_party/libc++/trunk/src/mutex.cpp",
-        "buildtools/third_party/libc++/trunk/src/mutex_destructor.cpp",
-        "buildtools/third_party/libc++/trunk/src/new.cpp",
-        "buildtools/third_party/libc++/trunk/src/optional.cpp",
-        "buildtools/third_party/libc++/trunk/src/random.cpp",
-        "buildtools/third_party/libc++/trunk/src/random_shuffle.cpp",
-        "buildtools/third_party/libc++/trunk/src/regex.cpp",
-        "buildtools/third_party/libc++/trunk/src/ryu/d2fixed.cpp",
-        "buildtools/third_party/libc++/trunk/src/ryu/d2s.cpp",
-        "buildtools/third_party/libc++/trunk/src/ryu/f2s.cpp",
-        "buildtools/third_party/libc++/trunk/src/shared_mutex.cpp",
-        "buildtools/third_party/libc++/trunk/src/stdexcept.cpp",
-        "buildtools/third_party/libc++/trunk/src/string.cpp",
-        "buildtools/third_party/libc++/trunk/src/strstream.cpp",
-        "buildtools/third_party/libc++/trunk/src/system_error.cpp",
-        "buildtools/third_party/libc++/trunk/src/thread.cpp",
-        "buildtools/third_party/libc++/trunk/src/typeinfo.cpp",
-        "buildtools/third_party/libc++/trunk/src/utility.cpp",
-        "buildtools/third_party/libc++/trunk/src/valarray.cpp",
-        "buildtools/third_party/libc++/trunk/src/variant.cpp",
-        "buildtools/third_party/libc++/trunk/src/vector.cpp",
-        "buildtools/third_party/libc++/trunk/src/verbose_abort.cpp",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DLIBCXX_BUILDING_LIBCXXABI",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_BUILDING_LIBRARY",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCPP_OVERRIDABLE_FUNC_VIS=__attribute__((__visibility__(\"default\")))",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++/trunk/src/",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    cppflags: [
-        "-fexceptions",
-    ],
-    rtti: true,
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //buildtools/third_party/libc++abi:libc++abi
-cc_object {
-    name: "cronet_aml_buildtools_third_party_libc__abi_libc__abi",
-    srcs: [
-        "buildtools/third_party/libc++abi/trunk/src/abort_message.cpp",
-        "buildtools/third_party/libc++abi/trunk/src/cxa_aux_runtime.cpp",
-        "buildtools/third_party/libc++abi/trunk/src/cxa_default_handlers.cpp",
-        "buildtools/third_party/libc++abi/trunk/src/cxa_exception.cpp",
-        "buildtools/third_party/libc++abi/trunk/src/cxa_exception_storage.cpp",
-        "buildtools/third_party/libc++abi/trunk/src/cxa_guard.cpp",
-        "buildtools/third_party/libc++abi/trunk/src/cxa_handlers.cpp",
-        "buildtools/third_party/libc++abi/trunk/src/cxa_personality.cpp",
-        "buildtools/third_party/libc++abi/trunk/src/cxa_thread_atexit.cpp",
-        "buildtools/third_party/libc++abi/trunk/src/cxa_vector.cpp",
-        "buildtools/third_party/libc++abi/trunk/src/cxa_virtual.cpp",
-        "buildtools/third_party/libc++abi/trunk/src/fallback_malloc.cpp",
-        "buildtools/third_party/libc++abi/trunk/src/private_typeinfo.cpp",
-        "buildtools/third_party/libc++abi/trunk/src/stdlib_exception.cpp",
-        "buildtools/third_party/libc++abi/trunk/src/stdlib_stdexcept.cpp",
-        "buildtools/third_party/libc++abi/trunk/src/stdlib_typeinfo.cpp",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DLIBCXXABI_SILENT_TERMINATE",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_BUILDING_LIBRARY",
-        "-D_LIBCPP_CONSTINIT=constinit",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++/trunk/src/",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    cppflags: [
-        "-fexceptions",
-    ],
-    rtti: true,
-    target: {
-        android_arm: {
-            srcs: [
-                "buildtools/third_party/libc++abi/cxa_demangle_stub.cc",
-            ],
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            srcs: [
-                "buildtools/third_party/libc++abi/cxa_demangle_stub.cc",
-            ],
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            srcs: [
-                "buildtools/third_party/libc++abi/cxa_demangle_stub.cc",
-            ],
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            srcs: [
-                "buildtools/third_party/libc++abi/cxa_demangle_stub.cc",
-            ],
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            srcs: [
-                "buildtools/third_party/libc++abi/trunk/src/cxa_demangle.cpp",
-            ],
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //buildtools/third_party/libunwind:libunwind
-cc_object {
-    name: "cronet_aml_buildtools_third_party_libunwind_libunwind",
-    srcs: [
-        "buildtools/third_party/libunwind/trunk/src/Unwind-EHABI.cpp",
-        "buildtools/third_party/libunwind/trunk/src/Unwind-sjlj.c",
-        "buildtools/third_party/libunwind/trunk/src/UnwindLevel1-gcc-ext.c",
-        "buildtools/third_party/libunwind/trunk/src/UnwindLevel1.c",
-        "buildtools/third_party/libunwind/trunk/src/UnwindRegistersRestore.S",
-        "buildtools/third_party/libunwind/trunk/src/UnwindRegistersSave.S",
-        "buildtools/third_party/libunwind/trunk/src/libunwind.cpp",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBUNWIND_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBUNWIND_IS_NATIVE_ONLY",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "buildtools/third_party/libunwind/trunk/include/",
-    ],
-    cpp_std: "c++20",
-    cppflags: [
-        "-fexceptions",
-    ],
-    rtti: true,
-    target: {
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //components/cronet/android:buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_components_cronet_android_buildflags__android_arm",
-    cmd: "echo '--flags INTEGRATED_MODE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//components/cronet/android:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "components/cronet/android/buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet/android:buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_components_cronet_android_buildflags__android_arm64",
-    cmd: "echo '--flags INTEGRATED_MODE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//components/cronet/android:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "components/cronet/android/buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet/android:buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_components_cronet_android_buildflags__android_x86",
-    cmd: "echo '--flags INTEGRATED_MODE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//components/cronet/android:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "components/cronet/android/buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet/android:buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_components_cronet_android_buildflags__android_x86_64",
-    cmd: "echo '--flags INTEGRATED_MODE=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//components/cronet/android:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "components/cronet/android/buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet/android:cronet
-cc_library_shared {
-    name: "cronet_aml_components_cronet_android_cronet",
-    srcs: [
-        ":cronet_aml_buildtools_third_party_libc___libc__",
-        ":cronet_aml_buildtools_third_party_libc__abi_libc__abi",
-        ":cronet_aml_buildtools_third_party_libunwind_libunwind",
-        ":cronet_aml_components_cronet_android_cronet_static",
-        ":cronet_aml_components_cronet_cronet_common",
-        ":cronet_aml_components_cronet_metrics_util",
-        ":cronet_aml_components_cronet_native_cronet_native_impl",
-        ":cronet_aml_components_grpc_support_grpc_support",
-        ":cronet_aml_components_metrics_library_support",
-        "components/cronet/android/cronet_jni.cc",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_components_prefs_prefs",
-        "cronet_aml_crypto_crypto",
-        "cronet_aml_net_net",
-        "cronet_aml_net_preload_decoder",
-        "cronet_aml_net_third_party_quiche_quiche",
-        "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_brotli_common",
-        "cronet_aml_third_party_brotli_dec",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-        "cronet_aml_third_party_protobuf_protobuf_lite",
-        "cronet_aml_third_party_zlib_zlib",
-        "cronet_aml_url_url",
-    ],
-    generated_headers: [
-        "cronet_aml_third_party_metrics_proto_metrics_proto_gen_headers",
-    ],
-    export_generated_headers: [
-        "cronet_aml_third_party_metrics_proto_metrics_proto_gen_headers",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "net/third_party/quiche/overrides/",
-        "net/third_party/quiche/src/",
-        "net/third_party/quiche/src/quiche/common/platform/default/",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-        "third_party/protobuf/src/",
-    ],
-    cpp_std: "c++20",
-    linker_scripts: [
-        "base/android/library_loader/anchor_functions.lds",
-    ],
-    stem: "libcronet.109.0.5386.0",
-    target: {
-        android_arm: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_components_cronet_android_buildflags__android_arm",
-                "cronet_aml_components_cronet_android_cronet_jni_headers__android_arm",
-                "cronet_aml_components_cronet_android_cronet_jni_registration__android_arm",
-                "cronet_aml_components_cronet_cronet_buildflags__android_arm",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_arm",
-                "cronet_aml_url_buildflags__android_arm",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_components_cronet_android_buildflags__android_arm",
-                "cronet_aml_components_cronet_android_cronet_jni_headers__android_arm",
-                "cronet_aml_components_cronet_android_cronet_jni_registration__android_arm",
-                "cronet_aml_components_cronet_cronet_buildflags__android_arm",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_arm",
-                "cronet_aml_url_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_components_cronet_android_buildflags__android_arm64",
-                "cronet_aml_components_cronet_android_cronet_jni_headers__android_arm64",
-                "cronet_aml_components_cronet_android_cronet_jni_registration__android_arm64",
-                "cronet_aml_components_cronet_cronet_buildflags__android_arm64",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_arm64",
-                "cronet_aml_url_buildflags__android_arm64",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_components_cronet_android_buildflags__android_arm64",
-                "cronet_aml_components_cronet_android_cronet_jni_headers__android_arm64",
-                "cronet_aml_components_cronet_android_cronet_jni_registration__android_arm64",
-                "cronet_aml_components_cronet_cronet_buildflags__android_arm64",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_arm64",
-                "cronet_aml_url_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_components_cronet_android_buildflags__android_x86",
-                "cronet_aml_components_cronet_android_cronet_jni_headers__android_x86",
-                "cronet_aml_components_cronet_android_cronet_jni_registration__android_x86",
-                "cronet_aml_components_cronet_cronet_buildflags__android_x86",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_x86",
-                "cronet_aml_url_buildflags__android_x86",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_components_cronet_android_buildflags__android_x86",
-                "cronet_aml_components_cronet_android_cronet_jni_headers__android_x86",
-                "cronet_aml_components_cronet_android_cronet_jni_registration__android_x86",
-                "cronet_aml_components_cronet_cronet_buildflags__android_x86",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_x86",
-                "cronet_aml_url_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_components_cronet_android_buildflags__android_x86_64",
-                "cronet_aml_components_cronet_android_cronet_jni_headers__android_x86_64",
-                "cronet_aml_components_cronet_android_cronet_jni_registration__android_x86_64",
-                "cronet_aml_components_cronet_cronet_buildflags__android_x86_64",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_x86_64",
-                "cronet_aml_url_buildflags__android_x86_64",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_components_cronet_android_buildflags__android_x86_64",
-                "cronet_aml_components_cronet_android_cronet_jni_headers__android_x86_64",
-                "cronet_aml_components_cronet_android_cronet_jni_registration__android_x86_64",
-                "cronet_aml_components_cronet_cronet_buildflags__android_x86_64",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_x86_64",
-                "cronet_aml_url_buildflags__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //components/cronet/android:cronet_jni_headers__android_arm
-cc_genrule {
-    name: "cronet_aml_components_cronet_android_cronet_jni_headers__android_arm",
-    srcs: [
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLibraryLoader.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/components/cronet/android/cronet_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "CronetBidirectionalStream_jni.h " +
-         "--output_name " +
-         "CronetLibraryLoader_jni.h " +
-         "--output_name " +
-         "CronetUploadDataStream_jni.h " +
-         "--output_name " +
-         "CronetUrlRequest_jni.h " +
-         "--output_name " +
-         "CronetUrlRequestContext_jni.h " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java) " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetLibraryLoader.java) " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java) " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java) " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java)",
-    out: [
-        "components/cronet/android/cronet_jni_headers/CronetBidirectionalStream_jni.h",
-        "components/cronet/android/cronet_jni_headers/CronetLibraryLoader_jni.h",
-        "components/cronet/android/cronet_jni_headers/CronetUploadDataStream_jni.h",
-        "components/cronet/android/cronet_jni_headers/CronetUrlRequestContext_jni.h",
-        "components/cronet/android/cronet_jni_headers/CronetUrlRequest_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet/android:cronet_jni_headers__android_arm64
-cc_genrule {
-    name: "cronet_aml_components_cronet_android_cronet_jni_headers__android_arm64",
-    srcs: [
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLibraryLoader.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/components/cronet/android/cronet_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "CronetBidirectionalStream_jni.h " +
-         "--output_name " +
-         "CronetLibraryLoader_jni.h " +
-         "--output_name " +
-         "CronetUploadDataStream_jni.h " +
-         "--output_name " +
-         "CronetUrlRequest_jni.h " +
-         "--output_name " +
-         "CronetUrlRequestContext_jni.h " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java) " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetLibraryLoader.java) " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java) " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java) " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java)",
-    out: [
-        "components/cronet/android/cronet_jni_headers/CronetBidirectionalStream_jni.h",
-        "components/cronet/android/cronet_jni_headers/CronetLibraryLoader_jni.h",
-        "components/cronet/android/cronet_jni_headers/CronetUploadDataStream_jni.h",
-        "components/cronet/android/cronet_jni_headers/CronetUrlRequestContext_jni.h",
-        "components/cronet/android/cronet_jni_headers/CronetUrlRequest_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet/android:cronet_jni_headers__android_x86
-cc_genrule {
-    name: "cronet_aml_components_cronet_android_cronet_jni_headers__android_x86",
-    srcs: [
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLibraryLoader.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/components/cronet/android/cronet_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "CronetBidirectionalStream_jni.h " +
-         "--output_name " +
-         "CronetLibraryLoader_jni.h " +
-         "--output_name " +
-         "CronetUploadDataStream_jni.h " +
-         "--output_name " +
-         "CronetUrlRequest_jni.h " +
-         "--output_name " +
-         "CronetUrlRequestContext_jni.h " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java) " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetLibraryLoader.java) " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java) " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java) " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java)",
-    out: [
-        "components/cronet/android/cronet_jni_headers/CronetBidirectionalStream_jni.h",
-        "components/cronet/android/cronet_jni_headers/CronetLibraryLoader_jni.h",
-        "components/cronet/android/cronet_jni_headers/CronetUploadDataStream_jni.h",
-        "components/cronet/android/cronet_jni_headers/CronetUrlRequestContext_jni.h",
-        "components/cronet/android/cronet_jni_headers/CronetUrlRequest_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet/android:cronet_jni_headers__android_x86_64
-cc_genrule {
-    name: "cronet_aml_components_cronet_android_cronet_jni_headers__android_x86_64",
-    srcs: [
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLibraryLoader.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/components/cronet/android/cronet_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "CronetBidirectionalStream_jni.h " +
-         "--output_name " +
-         "CronetLibraryLoader_jni.h " +
-         "--output_name " +
-         "CronetUploadDataStream_jni.h " +
-         "--output_name " +
-         "CronetUrlRequest_jni.h " +
-         "--output_name " +
-         "CronetUrlRequestContext_jni.h " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java) " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetLibraryLoader.java) " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java) " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java) " +
-         "--input_file " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java)",
-    out: [
-        "components/cronet/android/cronet_jni_headers/CronetBidirectionalStream_jni.h",
-        "components/cronet/android/cronet_jni_headers/CronetLibraryLoader_jni.h",
-        "components/cronet/android/cronet_jni_headers/CronetUploadDataStream_jni.h",
-        "components/cronet/android/cronet_jni_headers/CronetUrlRequestContext_jni.h",
-        "components/cronet/android/cronet_jni_headers/CronetUrlRequest_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet/android:cronet_jni_registration__android_arm
-cc_genrule {
-    name: "cronet_aml_components_cronet_android_cronet_jni_registration__android_arm",
-    srcs: [
-        "base/android/java/src/org/chromium/base/ActivityState.java",
-        "base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java",
-        "base/android/java/src/org/chromium/base/ApkAssets.java",
-        "base/android/java/src/org/chromium/base/ApplicationStatus.java",
-        "base/android/java/src/org/chromium/base/BaseFeatureList.java",
-        "base/android/java/src/org/chromium/base/BuildInfo.java",
-        "base/android/java/src/org/chromium/base/BundleUtils.java",
-        "base/android/java/src/org/chromium/base/ByteArrayGenerator.java",
-        "base/android/java/src/org/chromium/base/Callback.java",
-        "base/android/java/src/org/chromium/base/CallbackController.java",
-        "base/android/java/src/org/chromium/base/CollectionUtil.java",
-        "base/android/java/src/org/chromium/base/CommandLine.java",
-        "base/android/java/src/org/chromium/base/CommandLineInitUtil.java",
-        "base/android/java/src/org/chromium/base/Consumer.java",
-        "base/android/java/src/org/chromium/base/ContentUriUtils.java",
-        "base/android/java/src/org/chromium/base/ContextUtils.java",
-        "base/android/java/src/org/chromium/base/CpuFeatures.java",
-        "base/android/java/src/org/chromium/base/DiscardableReferencePool.java",
-        "base/android/java/src/org/chromium/base/EarlyTraceEvent.java",
-        "base/android/java/src/org/chromium/base/EventLog.java",
-        "base/android/java/src/org/chromium/base/FeatureList.java",
-        "base/android/java/src/org/chromium/base/Features.java",
-        "base/android/java/src/org/chromium/base/FieldTrialList.java",
-        "base/android/java/src/org/chromium/base/FileUtils.java",
-        "base/android/java/src/org/chromium/base/Function.java",
-        "base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java",
-        "base/android/java/src/org/chromium/base/IntStringCallback.java",
-        "base/android/java/src/org/chromium/base/IntentUtils.java",
-        "base/android/java/src/org/chromium/base/JNIUtils.java",
-        "base/android/java/src/org/chromium/base/JavaExceptionReporter.java",
-        "base/android/java/src/org/chromium/base/JavaHandlerThread.java",
-        "base/android/java/src/org/chromium/base/JniException.java",
-        "base/android/java/src/org/chromium/base/JniStaticTestMocker.java",
-        "base/android/java/src/org/chromium/base/LifetimeAssert.java",
-        "base/android/java/src/org/chromium/base/LocaleUtils.java",
-        "base/android/java/src/org/chromium/base/Log.java",
-        "base/android/java/src/org/chromium/base/MathUtils.java",
-        "base/android/java/src/org/chromium/base/MemoryPressureListener.java",
-        "base/android/java/src/org/chromium/base/NativeLibraryLoadedStatus.java",
-        "base/android/java/src/org/chromium/base/ObserverList.java",
-        "base/android/java/src/org/chromium/base/PackageManagerUtils.java",
-        "base/android/java/src/org/chromium/base/PackageUtils.java",
-        "base/android/java/src/org/chromium/base/PathService.java",
-        "base/android/java/src/org/chromium/base/PathUtils.java",
-        "base/android/java/src/org/chromium/base/PiiElider.java",
-        "base/android/java/src/org/chromium/base/PowerMonitor.java",
-        "base/android/java/src/org/chromium/base/PowerMonitorForQ.java",
-        "base/android/java/src/org/chromium/base/Predicate.java",
-        "base/android/java/src/org/chromium/base/Promise.java",
-        "base/android/java/src/org/chromium/base/RadioUtils.java",
-        "base/android/java/src/org/chromium/base/StreamUtil.java",
-        "base/android/java/src/org/chromium/base/StrictModeContext.java",
-        "base/android/java/src/org/chromium/base/SysUtils.java",
-        "base/android/java/src/org/chromium/base/ThreadUtils.java",
-        "base/android/java/src/org/chromium/base/TimeUtils.java",
-        "base/android/java/src/org/chromium/base/TimezoneUtils.java",
-        "base/android/java/src/org/chromium/base/TraceEvent.java",
-        "base/android/java/src/org/chromium/base/UnguessableToken.java",
-        "base/android/java/src/org/chromium/base/UnownedUserData.java",
-        "base/android/java/src/org/chromium/base/UnownedUserDataHost.java",
-        "base/android/java/src/org/chromium/base/UnownedUserDataKey.java",
-        "base/android/java/src/org/chromium/base/UserData.java",
-        "base/android/java/src/org/chromium/base/UserDataHost.java",
-        "base/android/java/src/org/chromium/base/WrappedClassLoader.java",
-        "base/android/java/src/org/chromium/base/annotations/AccessedByNative.java",
-        "base/android/java/src/org/chromium/base/annotations/CalledByNative.java",
-        "base/android/java/src/org/chromium/base/annotations/CalledByNativeForTesting.java",
-        "base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java",
-        "base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java",
-        "base/android/java/src/org/chromium/base/annotations/JNINamespace.java",
-        "base/android/java/src/org/chromium/base/annotations/JniIgnoreNatives.java",
-        "base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java",
-        "base/android/java/src/org/chromium/base/annotations/NativeMethods.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForM.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForN.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForO.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForOMR1.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForP.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForQ.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForR.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForS.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/DummyJankTracker.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/FrameMetrics.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsListener.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsStore.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankActivityTracker.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetricCalculator.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetrics.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankReportingRunnable.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankReportingScheduler.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankScenario.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankTracker.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankTrackerImpl.java",
-        "base/android/java/src/org/chromium/base/library_loader/LegacyLinker.java",
-        "base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
-        "base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java",
-        "base/android/java/src/org/chromium/base/library_loader/Linker.java",
-        "base/android/java/src/org/chromium/base/library_loader/LinkerJni.java",
-        "base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java",
-        "base/android/java/src/org/chromium/base/library_loader/ModernLinker.java",
-        "base/android/java/src/org/chromium/base/library_loader/ModernLinkerJni.java",
-        "base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java",
-        "base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java",
-        "base/android/java/src/org/chromium/base/lifetime/DestroyChecker.java",
-        "base/android/java/src/org/chromium/base/lifetime/Destroyable.java",
-        "base/android/java/src/org/chromium/base/memory/JavaHeapDumpGenerator.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPurgeManager.java",
-        "base/android/java/src/org/chromium/base/metrics/CachingUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/NativeUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/NoopUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/RecordHistogram.java",
-        "base/android/java/src/org/chromium/base/metrics/RecordUserAction.java",
-        "base/android/java/src/org/chromium/base/metrics/ScopedSysTraceEvent.java",
-        "base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java",
-        "base/android/java/src/org/chromium/base/metrics/TimingMetric.java",
-        "base/android/java/src/org/chromium/base/metrics/UmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/UmaRecorderHolder.java",
-        "base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java",
-        "base/android/java/src/org/chromium/base/process_launcher/BindService.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnection.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionDelegate.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionFactory.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionImpl.java",
-        "base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java",
-        "base/android/java/src/org/chromium/base/supplier/BooleanSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/DestroyableObservableSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/ObservableSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/ObservableSupplierImpl.java",
-        "base/android/java/src/org/chromium/base/supplier/OneShotCallback.java",
-        "base/android/java/src/org/chromium/base/supplier/OneshotSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/OneshotSupplierImpl.java",
-        "base/android/java/src/org/chromium/base/supplier/Supplier.java",
-        "base/android/java/src/org/chromium/base/supplier/UnownedUserDataSupplier.java",
-        "base/android/java/src/org/chromium/base/task/AsyncTask.java",
-        "base/android/java/src/org/chromium/base/task/BackgroundOnlyAsyncTask.java",
-        "base/android/java/src/org/chromium/base/task/ChainedTasks.java",
-        "base/android/java/src/org/chromium/base/task/ChoreographerTaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/ChromeThreadPoolExecutor.java",
-        "base/android/java/src/org/chromium/base/task/DefaultTaskExecutor.java",
-        "base/android/java/src/org/chromium/base/task/PostTask.java",
-        "base/android/java/src/org/chromium/base/task/SequencedTaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/SequencedTaskRunnerImpl.java",
-        "base/android/java/src/org/chromium/base/task/SerialExecutor.java",
-        "base/android/java/src/org/chromium/base/task/SingleThreadTaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/SingleThreadTaskRunnerImpl.java",
-        "base/android/java/src/org/chromium/base/task/TaskExecutor.java",
-        "base/android/java/src/org/chromium/base/task/TaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java",
-        "base/android/java/src/org/chromium/base/task/TaskTraits.java",
-        "base/android/java/src/org/chromium/base/task/TaskTraitsExtensionDescriptor.java",
-        "build/android/java/src/org/chromium/build/annotations/AlwaysInline.java",
-        "build/android/java/src/org/chromium/build/annotations/CheckDiscard.java",
-        "build/android/java/src/org/chromium/build/annotations/DoNotClassMerge.java",
-        "build/android/java/src/org/chromium/build/annotations/DoNotInline.java",
-        "build/android/java/src/org/chromium/build/annotations/IdentifierNameString.java",
-        "build/android/java/src/org/chromium/build/annotations/MainDex.java",
-        "build/android/java/src/org/chromium/build/annotations/MockedInTests.java",
-        "build/android/java/src/org/chromium/build/annotations/UsedByReflection.java",
-        "components/cronet/android/api/src/org/chromium/net/BidirectionalStream.java",
-        "components/cronet/android/api/src/org/chromium/net/CallbackException.java",
-        "components/cronet/android/api/src/org/chromium/net/CronetEngine.java",
-        "components/cronet/android/api/src/org/chromium/net/CronetException.java",
-        "components/cronet/android/api/src/org/chromium/net/CronetProvider.java",
-        "components/cronet/android/api/src/org/chromium/net/ExperimentalBidirectionalStream.java",
-        "components/cronet/android/api/src/org/chromium/net/ExperimentalCronetEngine.java",
-        "components/cronet/android/api/src/org/chromium/net/ExperimentalUrlRequest.java",
-        "components/cronet/android/api/src/org/chromium/net/ICronetEngineBuilder.java",
-        "components/cronet/android/api/src/org/chromium/net/InlineExecutionProhibitedException.java",
-        "components/cronet/android/api/src/org/chromium/net/NetworkException.java",
-        "components/cronet/android/api/src/org/chromium/net/NetworkQualityRttListener.java",
-        "components/cronet/android/api/src/org/chromium/net/NetworkQualityThroughputListener.java",
-        "components/cronet/android/api/src/org/chromium/net/QuicException.java",
-        "components/cronet/android/api/src/org/chromium/net/RequestFinishedInfo.java",
-        "components/cronet/android/api/src/org/chromium/net/UploadDataProvider.java",
-        "components/cronet/android/api/src/org/chromium/net/UploadDataProviders.java",
-        "components/cronet/android/api/src/org/chromium/net/UploadDataSink.java",
-        "components/cronet/android/api/src/org/chromium/net/UrlRequest.java",
-        "components/cronet/android/api/src/org/chromium/net/UrlResponseInfo.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/ByteArrayCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/ContentTypeParametersParser.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/CronetRequestCompletionListener.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/CronetResponse.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/ImplicitFlowControlCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/InMemoryTransformCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/JsonCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/RedirectHandler.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/RedirectHandlers.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/StringCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/UploadDataProviders.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/UrlRequestCallbacks.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetController.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetEngine.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetProvider.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeUrlRequest.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeUrlResponse.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/ResponseMatcher.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/UrlResponseMatcher.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/BidirectionalStreamBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/BidirectionalStreamNetworkException.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CallbackExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBase.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLibraryLoader.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLogger.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLoggerFactory.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetManifest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetMetrics.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/InputStreamChannel.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngine.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngineBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetProvider.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaUploadDataSinkBase.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequestUtils.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderWithLibraryLoaderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetProvider.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NoOpLogger.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/Preconditions.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/QuicExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/RequestFinishedInfoImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UrlRequestBase.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UrlRequestBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UrlResponseInfoImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UserAgent.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/VersionSafeCallbacks.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetBufferedOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetChunkedOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetFixedModeOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLConnection.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLStreamHandler.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetInputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetURLStreamHandlerFactory.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/MessageLoop.java",
-        "net/android/java/src/org/chromium/net/AndroidCertVerifyResult.java",
-        "net/android/java/src/org/chromium/net/AndroidKeyStore.java",
-        "net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java",
-        "net/android/java/src/org/chromium/net/AndroidTrafficStats.java",
-        "net/android/java/src/org/chromium/net/ChromiumNetworkAdapter.java",
-        "net/android/java/src/org/chromium/net/DnsStatus.java",
-        "net/android/java/src/org/chromium/net/GURLUtils.java",
-        "net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java",
-        "net/android/java/src/org/chromium/net/HttpNegotiateConstants.java",
-        "net/android/java/src/org/chromium/net/HttpUtil.java",
-        "net/android/java/src/org/chromium/net/MimeTypeFilter.java",
-        "net/android/java/src/org/chromium/net/NetStringUtil.java",
-        "net/android/java/src/org/chromium/net/NetworkActiveNotifier.java",
-        "net/android/java/src/org/chromium/net/NetworkChangeNotifier.java",
-        "net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java",
-        "net/android/java/src/org/chromium/net/NetworkTrafficAnnotationTag.java",
-        "net/android/java/src/org/chromium/net/ProxyBroadcastReceiver.java",
-        "net/android/java/src/org/chromium/net/ProxyChangeListener.java",
-        "net/android/java/src/org/chromium/net/RegistrationPolicyAlwaysRegister.java",
-        "net/android/java/src/org/chromium/net/RegistrationPolicyApplicationStatus.java",
-        "net/android/java/src/org/chromium/net/ThreadStatsUid.java",
-        "net/android/java/src/org/chromium/net/X509Util.java",
-        "url/android/java/src/org/chromium/url/IDNStringUtil.java",
-    ],
-    cmd: "current_dir=`basename \\`pwd\\``; " +
-         "for f in $(in); " +
-         "do " +
-         "echo \"../$$current_dir/$$f\" >> $(genDir)/java.sources; " +
-         "done; " +
-         "python3 $(location base/android/jni_generator/jni_registration_generator.py) --srcjar-path " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.srcjar " +
-         "--depfile " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.d " +
-         "--sources-files " +
-         "$(genDir)/java.sources " +
-         "--include_test_only " +
-         "--use_proxy_hash " +
-         "--header-path " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.h " +
-         "--manual_jni_registration " +
-         ";sed -i -e 's/OUT_SOONG_.TEMP_SBOX_.*_OUT/GEN/g'  " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.h",
-    out: [
-        "components/cronet/android/cronet_jni_registration.h",
-        "components/cronet/android/cronet_jni_registration.srcjar",
-    ],
-    tool_files: [
-        "base/android/jni_generator/jni_generator.py",
-        "base/android/jni_generator/jni_registration_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet/android:cronet_jni_registration__android_arm64
-cc_genrule {
-    name: "cronet_aml_components_cronet_android_cronet_jni_registration__android_arm64",
-    srcs: [
-        "base/android/java/src/org/chromium/base/ActivityState.java",
-        "base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java",
-        "base/android/java/src/org/chromium/base/ApkAssets.java",
-        "base/android/java/src/org/chromium/base/ApplicationStatus.java",
-        "base/android/java/src/org/chromium/base/BaseFeatureList.java",
-        "base/android/java/src/org/chromium/base/BuildInfo.java",
-        "base/android/java/src/org/chromium/base/BundleUtils.java",
-        "base/android/java/src/org/chromium/base/ByteArrayGenerator.java",
-        "base/android/java/src/org/chromium/base/Callback.java",
-        "base/android/java/src/org/chromium/base/CallbackController.java",
-        "base/android/java/src/org/chromium/base/CollectionUtil.java",
-        "base/android/java/src/org/chromium/base/CommandLine.java",
-        "base/android/java/src/org/chromium/base/CommandLineInitUtil.java",
-        "base/android/java/src/org/chromium/base/Consumer.java",
-        "base/android/java/src/org/chromium/base/ContentUriUtils.java",
-        "base/android/java/src/org/chromium/base/ContextUtils.java",
-        "base/android/java/src/org/chromium/base/CpuFeatures.java",
-        "base/android/java/src/org/chromium/base/DiscardableReferencePool.java",
-        "base/android/java/src/org/chromium/base/EarlyTraceEvent.java",
-        "base/android/java/src/org/chromium/base/EventLog.java",
-        "base/android/java/src/org/chromium/base/FeatureList.java",
-        "base/android/java/src/org/chromium/base/Features.java",
-        "base/android/java/src/org/chromium/base/FieldTrialList.java",
-        "base/android/java/src/org/chromium/base/FileUtils.java",
-        "base/android/java/src/org/chromium/base/Function.java",
-        "base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java",
-        "base/android/java/src/org/chromium/base/IntStringCallback.java",
-        "base/android/java/src/org/chromium/base/IntentUtils.java",
-        "base/android/java/src/org/chromium/base/JNIUtils.java",
-        "base/android/java/src/org/chromium/base/JavaExceptionReporter.java",
-        "base/android/java/src/org/chromium/base/JavaHandlerThread.java",
-        "base/android/java/src/org/chromium/base/JniException.java",
-        "base/android/java/src/org/chromium/base/JniStaticTestMocker.java",
-        "base/android/java/src/org/chromium/base/LifetimeAssert.java",
-        "base/android/java/src/org/chromium/base/LocaleUtils.java",
-        "base/android/java/src/org/chromium/base/Log.java",
-        "base/android/java/src/org/chromium/base/MathUtils.java",
-        "base/android/java/src/org/chromium/base/MemoryPressureListener.java",
-        "base/android/java/src/org/chromium/base/NativeLibraryLoadedStatus.java",
-        "base/android/java/src/org/chromium/base/ObserverList.java",
-        "base/android/java/src/org/chromium/base/PackageManagerUtils.java",
-        "base/android/java/src/org/chromium/base/PackageUtils.java",
-        "base/android/java/src/org/chromium/base/PathService.java",
-        "base/android/java/src/org/chromium/base/PathUtils.java",
-        "base/android/java/src/org/chromium/base/PiiElider.java",
-        "base/android/java/src/org/chromium/base/PowerMonitor.java",
-        "base/android/java/src/org/chromium/base/PowerMonitorForQ.java",
-        "base/android/java/src/org/chromium/base/Predicate.java",
-        "base/android/java/src/org/chromium/base/Promise.java",
-        "base/android/java/src/org/chromium/base/RadioUtils.java",
-        "base/android/java/src/org/chromium/base/StreamUtil.java",
-        "base/android/java/src/org/chromium/base/StrictModeContext.java",
-        "base/android/java/src/org/chromium/base/SysUtils.java",
-        "base/android/java/src/org/chromium/base/ThreadUtils.java",
-        "base/android/java/src/org/chromium/base/TimeUtils.java",
-        "base/android/java/src/org/chromium/base/TimezoneUtils.java",
-        "base/android/java/src/org/chromium/base/TraceEvent.java",
-        "base/android/java/src/org/chromium/base/UnguessableToken.java",
-        "base/android/java/src/org/chromium/base/UnownedUserData.java",
-        "base/android/java/src/org/chromium/base/UnownedUserDataHost.java",
-        "base/android/java/src/org/chromium/base/UnownedUserDataKey.java",
-        "base/android/java/src/org/chromium/base/UserData.java",
-        "base/android/java/src/org/chromium/base/UserDataHost.java",
-        "base/android/java/src/org/chromium/base/WrappedClassLoader.java",
-        "base/android/java/src/org/chromium/base/annotations/AccessedByNative.java",
-        "base/android/java/src/org/chromium/base/annotations/CalledByNative.java",
-        "base/android/java/src/org/chromium/base/annotations/CalledByNativeForTesting.java",
-        "base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java",
-        "base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java",
-        "base/android/java/src/org/chromium/base/annotations/JNINamespace.java",
-        "base/android/java/src/org/chromium/base/annotations/JniIgnoreNatives.java",
-        "base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java",
-        "base/android/java/src/org/chromium/base/annotations/NativeMethods.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForM.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForN.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForO.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForOMR1.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForP.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForQ.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForR.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForS.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/DummyJankTracker.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/FrameMetrics.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsListener.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsStore.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankActivityTracker.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetricCalculator.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetrics.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankReportingRunnable.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankReportingScheduler.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankScenario.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankTracker.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankTrackerImpl.java",
-        "base/android/java/src/org/chromium/base/library_loader/LegacyLinker.java",
-        "base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
-        "base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java",
-        "base/android/java/src/org/chromium/base/library_loader/Linker.java",
-        "base/android/java/src/org/chromium/base/library_loader/LinkerJni.java",
-        "base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java",
-        "base/android/java/src/org/chromium/base/library_loader/ModernLinker.java",
-        "base/android/java/src/org/chromium/base/library_loader/ModernLinkerJni.java",
-        "base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java",
-        "base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java",
-        "base/android/java/src/org/chromium/base/lifetime/DestroyChecker.java",
-        "base/android/java/src/org/chromium/base/lifetime/Destroyable.java",
-        "base/android/java/src/org/chromium/base/memory/JavaHeapDumpGenerator.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPurgeManager.java",
-        "base/android/java/src/org/chromium/base/metrics/CachingUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/NativeUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/NoopUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/RecordHistogram.java",
-        "base/android/java/src/org/chromium/base/metrics/RecordUserAction.java",
-        "base/android/java/src/org/chromium/base/metrics/ScopedSysTraceEvent.java",
-        "base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java",
-        "base/android/java/src/org/chromium/base/metrics/TimingMetric.java",
-        "base/android/java/src/org/chromium/base/metrics/UmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/UmaRecorderHolder.java",
-        "base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java",
-        "base/android/java/src/org/chromium/base/process_launcher/BindService.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnection.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionDelegate.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionFactory.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionImpl.java",
-        "base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java",
-        "base/android/java/src/org/chromium/base/supplier/BooleanSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/DestroyableObservableSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/ObservableSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/ObservableSupplierImpl.java",
-        "base/android/java/src/org/chromium/base/supplier/OneShotCallback.java",
-        "base/android/java/src/org/chromium/base/supplier/OneshotSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/OneshotSupplierImpl.java",
-        "base/android/java/src/org/chromium/base/supplier/Supplier.java",
-        "base/android/java/src/org/chromium/base/supplier/UnownedUserDataSupplier.java",
-        "base/android/java/src/org/chromium/base/task/AsyncTask.java",
-        "base/android/java/src/org/chromium/base/task/BackgroundOnlyAsyncTask.java",
-        "base/android/java/src/org/chromium/base/task/ChainedTasks.java",
-        "base/android/java/src/org/chromium/base/task/ChoreographerTaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/ChromeThreadPoolExecutor.java",
-        "base/android/java/src/org/chromium/base/task/DefaultTaskExecutor.java",
-        "base/android/java/src/org/chromium/base/task/PostTask.java",
-        "base/android/java/src/org/chromium/base/task/SequencedTaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/SequencedTaskRunnerImpl.java",
-        "base/android/java/src/org/chromium/base/task/SerialExecutor.java",
-        "base/android/java/src/org/chromium/base/task/SingleThreadTaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/SingleThreadTaskRunnerImpl.java",
-        "base/android/java/src/org/chromium/base/task/TaskExecutor.java",
-        "base/android/java/src/org/chromium/base/task/TaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java",
-        "base/android/java/src/org/chromium/base/task/TaskTraits.java",
-        "base/android/java/src/org/chromium/base/task/TaskTraitsExtensionDescriptor.java",
-        "build/android/java/src/org/chromium/build/annotations/AlwaysInline.java",
-        "build/android/java/src/org/chromium/build/annotations/CheckDiscard.java",
-        "build/android/java/src/org/chromium/build/annotations/DoNotClassMerge.java",
-        "build/android/java/src/org/chromium/build/annotations/DoNotInline.java",
-        "build/android/java/src/org/chromium/build/annotations/IdentifierNameString.java",
-        "build/android/java/src/org/chromium/build/annotations/MainDex.java",
-        "build/android/java/src/org/chromium/build/annotations/MockedInTests.java",
-        "build/android/java/src/org/chromium/build/annotations/UsedByReflection.java",
-        "components/cronet/android/api/src/org/chromium/net/BidirectionalStream.java",
-        "components/cronet/android/api/src/org/chromium/net/CallbackException.java",
-        "components/cronet/android/api/src/org/chromium/net/CronetEngine.java",
-        "components/cronet/android/api/src/org/chromium/net/CronetException.java",
-        "components/cronet/android/api/src/org/chromium/net/CronetProvider.java",
-        "components/cronet/android/api/src/org/chromium/net/ExperimentalBidirectionalStream.java",
-        "components/cronet/android/api/src/org/chromium/net/ExperimentalCronetEngine.java",
-        "components/cronet/android/api/src/org/chromium/net/ExperimentalUrlRequest.java",
-        "components/cronet/android/api/src/org/chromium/net/ICronetEngineBuilder.java",
-        "components/cronet/android/api/src/org/chromium/net/InlineExecutionProhibitedException.java",
-        "components/cronet/android/api/src/org/chromium/net/NetworkException.java",
-        "components/cronet/android/api/src/org/chromium/net/NetworkQualityRttListener.java",
-        "components/cronet/android/api/src/org/chromium/net/NetworkQualityThroughputListener.java",
-        "components/cronet/android/api/src/org/chromium/net/QuicException.java",
-        "components/cronet/android/api/src/org/chromium/net/RequestFinishedInfo.java",
-        "components/cronet/android/api/src/org/chromium/net/UploadDataProvider.java",
-        "components/cronet/android/api/src/org/chromium/net/UploadDataProviders.java",
-        "components/cronet/android/api/src/org/chromium/net/UploadDataSink.java",
-        "components/cronet/android/api/src/org/chromium/net/UrlRequest.java",
-        "components/cronet/android/api/src/org/chromium/net/UrlResponseInfo.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/ByteArrayCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/ContentTypeParametersParser.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/CronetRequestCompletionListener.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/CronetResponse.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/ImplicitFlowControlCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/InMemoryTransformCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/JsonCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/RedirectHandler.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/RedirectHandlers.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/StringCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/UploadDataProviders.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/UrlRequestCallbacks.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetController.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetEngine.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetProvider.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeUrlRequest.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeUrlResponse.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/ResponseMatcher.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/UrlResponseMatcher.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/BidirectionalStreamBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/BidirectionalStreamNetworkException.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CallbackExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBase.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLibraryLoader.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLogger.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLoggerFactory.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetManifest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetMetrics.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/InputStreamChannel.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngine.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngineBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetProvider.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaUploadDataSinkBase.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequestUtils.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderWithLibraryLoaderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetProvider.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NoOpLogger.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/Preconditions.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/QuicExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/RequestFinishedInfoImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UrlRequestBase.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UrlRequestBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UrlResponseInfoImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UserAgent.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/VersionSafeCallbacks.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetBufferedOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetChunkedOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetFixedModeOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLConnection.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLStreamHandler.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetInputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetURLStreamHandlerFactory.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/MessageLoop.java",
-        "net/android/java/src/org/chromium/net/AndroidCertVerifyResult.java",
-        "net/android/java/src/org/chromium/net/AndroidKeyStore.java",
-        "net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java",
-        "net/android/java/src/org/chromium/net/AndroidTrafficStats.java",
-        "net/android/java/src/org/chromium/net/ChromiumNetworkAdapter.java",
-        "net/android/java/src/org/chromium/net/DnsStatus.java",
-        "net/android/java/src/org/chromium/net/GURLUtils.java",
-        "net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java",
-        "net/android/java/src/org/chromium/net/HttpNegotiateConstants.java",
-        "net/android/java/src/org/chromium/net/HttpUtil.java",
-        "net/android/java/src/org/chromium/net/MimeTypeFilter.java",
-        "net/android/java/src/org/chromium/net/NetStringUtil.java",
-        "net/android/java/src/org/chromium/net/NetworkActiveNotifier.java",
-        "net/android/java/src/org/chromium/net/NetworkChangeNotifier.java",
-        "net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java",
-        "net/android/java/src/org/chromium/net/NetworkTrafficAnnotationTag.java",
-        "net/android/java/src/org/chromium/net/ProxyBroadcastReceiver.java",
-        "net/android/java/src/org/chromium/net/ProxyChangeListener.java",
-        "net/android/java/src/org/chromium/net/RegistrationPolicyAlwaysRegister.java",
-        "net/android/java/src/org/chromium/net/RegistrationPolicyApplicationStatus.java",
-        "net/android/java/src/org/chromium/net/ThreadStatsUid.java",
-        "net/android/java/src/org/chromium/net/X509Util.java",
-        "url/android/java/src/org/chromium/url/IDNStringUtil.java",
-    ],
-    cmd: "current_dir=`basename \\`pwd\\``; " +
-         "for f in $(in); " +
-         "do " +
-         "echo \"../$$current_dir/$$f\" >> $(genDir)/java.sources; " +
-         "done; " +
-         "python3 $(location base/android/jni_generator/jni_registration_generator.py) --srcjar-path " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.srcjar " +
-         "--depfile " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.d " +
-         "--sources-files " +
-         "$(genDir)/java.sources " +
-         "--include_test_only " +
-         "--use_proxy_hash " +
-         "--header-path " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.h " +
-         "--manual_jni_registration " +
-         ";sed -i -e 's/OUT_SOONG_.TEMP_SBOX_.*_OUT/GEN/g'  " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.h",
-    out: [
-        "components/cronet/android/cronet_jni_registration.h",
-        "components/cronet/android/cronet_jni_registration.srcjar",
-    ],
-    tool_files: [
-        "base/android/jni_generator/jni_generator.py",
-        "base/android/jni_generator/jni_registration_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet/android:cronet_jni_registration__android_x86
-cc_genrule {
-    name: "cronet_aml_components_cronet_android_cronet_jni_registration__android_x86",
-    srcs: [
-        "base/android/java/src/org/chromium/base/ActivityState.java",
-        "base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java",
-        "base/android/java/src/org/chromium/base/ApkAssets.java",
-        "base/android/java/src/org/chromium/base/ApplicationStatus.java",
-        "base/android/java/src/org/chromium/base/BaseFeatureList.java",
-        "base/android/java/src/org/chromium/base/BuildInfo.java",
-        "base/android/java/src/org/chromium/base/BundleUtils.java",
-        "base/android/java/src/org/chromium/base/ByteArrayGenerator.java",
-        "base/android/java/src/org/chromium/base/Callback.java",
-        "base/android/java/src/org/chromium/base/CallbackController.java",
-        "base/android/java/src/org/chromium/base/CollectionUtil.java",
-        "base/android/java/src/org/chromium/base/CommandLine.java",
-        "base/android/java/src/org/chromium/base/CommandLineInitUtil.java",
-        "base/android/java/src/org/chromium/base/Consumer.java",
-        "base/android/java/src/org/chromium/base/ContentUriUtils.java",
-        "base/android/java/src/org/chromium/base/ContextUtils.java",
-        "base/android/java/src/org/chromium/base/CpuFeatures.java",
-        "base/android/java/src/org/chromium/base/DiscardableReferencePool.java",
-        "base/android/java/src/org/chromium/base/EarlyTraceEvent.java",
-        "base/android/java/src/org/chromium/base/EventLog.java",
-        "base/android/java/src/org/chromium/base/FeatureList.java",
-        "base/android/java/src/org/chromium/base/Features.java",
-        "base/android/java/src/org/chromium/base/FieldTrialList.java",
-        "base/android/java/src/org/chromium/base/FileUtils.java",
-        "base/android/java/src/org/chromium/base/Function.java",
-        "base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java",
-        "base/android/java/src/org/chromium/base/IntStringCallback.java",
-        "base/android/java/src/org/chromium/base/IntentUtils.java",
-        "base/android/java/src/org/chromium/base/JNIUtils.java",
-        "base/android/java/src/org/chromium/base/JavaExceptionReporter.java",
-        "base/android/java/src/org/chromium/base/JavaHandlerThread.java",
-        "base/android/java/src/org/chromium/base/JniException.java",
-        "base/android/java/src/org/chromium/base/JniStaticTestMocker.java",
-        "base/android/java/src/org/chromium/base/LifetimeAssert.java",
-        "base/android/java/src/org/chromium/base/LocaleUtils.java",
-        "base/android/java/src/org/chromium/base/Log.java",
-        "base/android/java/src/org/chromium/base/MathUtils.java",
-        "base/android/java/src/org/chromium/base/MemoryPressureListener.java",
-        "base/android/java/src/org/chromium/base/NativeLibraryLoadedStatus.java",
-        "base/android/java/src/org/chromium/base/ObserverList.java",
-        "base/android/java/src/org/chromium/base/PackageManagerUtils.java",
-        "base/android/java/src/org/chromium/base/PackageUtils.java",
-        "base/android/java/src/org/chromium/base/PathService.java",
-        "base/android/java/src/org/chromium/base/PathUtils.java",
-        "base/android/java/src/org/chromium/base/PiiElider.java",
-        "base/android/java/src/org/chromium/base/PowerMonitor.java",
-        "base/android/java/src/org/chromium/base/PowerMonitorForQ.java",
-        "base/android/java/src/org/chromium/base/Predicate.java",
-        "base/android/java/src/org/chromium/base/Promise.java",
-        "base/android/java/src/org/chromium/base/RadioUtils.java",
-        "base/android/java/src/org/chromium/base/StreamUtil.java",
-        "base/android/java/src/org/chromium/base/StrictModeContext.java",
-        "base/android/java/src/org/chromium/base/SysUtils.java",
-        "base/android/java/src/org/chromium/base/ThreadUtils.java",
-        "base/android/java/src/org/chromium/base/TimeUtils.java",
-        "base/android/java/src/org/chromium/base/TimezoneUtils.java",
-        "base/android/java/src/org/chromium/base/TraceEvent.java",
-        "base/android/java/src/org/chromium/base/UnguessableToken.java",
-        "base/android/java/src/org/chromium/base/UnownedUserData.java",
-        "base/android/java/src/org/chromium/base/UnownedUserDataHost.java",
-        "base/android/java/src/org/chromium/base/UnownedUserDataKey.java",
-        "base/android/java/src/org/chromium/base/UserData.java",
-        "base/android/java/src/org/chromium/base/UserDataHost.java",
-        "base/android/java/src/org/chromium/base/WrappedClassLoader.java",
-        "base/android/java/src/org/chromium/base/annotations/AccessedByNative.java",
-        "base/android/java/src/org/chromium/base/annotations/CalledByNative.java",
-        "base/android/java/src/org/chromium/base/annotations/CalledByNativeForTesting.java",
-        "base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java",
-        "base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java",
-        "base/android/java/src/org/chromium/base/annotations/JNINamespace.java",
-        "base/android/java/src/org/chromium/base/annotations/JniIgnoreNatives.java",
-        "base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java",
-        "base/android/java/src/org/chromium/base/annotations/NativeMethods.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForM.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForN.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForO.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForOMR1.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForP.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForQ.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForR.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForS.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/DummyJankTracker.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/FrameMetrics.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsListener.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsStore.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankActivityTracker.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetricCalculator.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetrics.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankReportingRunnable.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankReportingScheduler.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankScenario.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankTracker.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankTrackerImpl.java",
-        "base/android/java/src/org/chromium/base/library_loader/LegacyLinker.java",
-        "base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
-        "base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java",
-        "base/android/java/src/org/chromium/base/library_loader/Linker.java",
-        "base/android/java/src/org/chromium/base/library_loader/LinkerJni.java",
-        "base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java",
-        "base/android/java/src/org/chromium/base/library_loader/ModernLinker.java",
-        "base/android/java/src/org/chromium/base/library_loader/ModernLinkerJni.java",
-        "base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java",
-        "base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java",
-        "base/android/java/src/org/chromium/base/lifetime/DestroyChecker.java",
-        "base/android/java/src/org/chromium/base/lifetime/Destroyable.java",
-        "base/android/java/src/org/chromium/base/memory/JavaHeapDumpGenerator.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPurgeManager.java",
-        "base/android/java/src/org/chromium/base/metrics/CachingUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/NativeUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/NoopUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/RecordHistogram.java",
-        "base/android/java/src/org/chromium/base/metrics/RecordUserAction.java",
-        "base/android/java/src/org/chromium/base/metrics/ScopedSysTraceEvent.java",
-        "base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java",
-        "base/android/java/src/org/chromium/base/metrics/TimingMetric.java",
-        "base/android/java/src/org/chromium/base/metrics/UmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/UmaRecorderHolder.java",
-        "base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java",
-        "base/android/java/src/org/chromium/base/process_launcher/BindService.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnection.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionDelegate.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionFactory.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionImpl.java",
-        "base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java",
-        "base/android/java/src/org/chromium/base/supplier/BooleanSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/DestroyableObservableSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/ObservableSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/ObservableSupplierImpl.java",
-        "base/android/java/src/org/chromium/base/supplier/OneShotCallback.java",
-        "base/android/java/src/org/chromium/base/supplier/OneshotSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/OneshotSupplierImpl.java",
-        "base/android/java/src/org/chromium/base/supplier/Supplier.java",
-        "base/android/java/src/org/chromium/base/supplier/UnownedUserDataSupplier.java",
-        "base/android/java/src/org/chromium/base/task/AsyncTask.java",
-        "base/android/java/src/org/chromium/base/task/BackgroundOnlyAsyncTask.java",
-        "base/android/java/src/org/chromium/base/task/ChainedTasks.java",
-        "base/android/java/src/org/chromium/base/task/ChoreographerTaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/ChromeThreadPoolExecutor.java",
-        "base/android/java/src/org/chromium/base/task/DefaultTaskExecutor.java",
-        "base/android/java/src/org/chromium/base/task/PostTask.java",
-        "base/android/java/src/org/chromium/base/task/SequencedTaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/SequencedTaskRunnerImpl.java",
-        "base/android/java/src/org/chromium/base/task/SerialExecutor.java",
-        "base/android/java/src/org/chromium/base/task/SingleThreadTaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/SingleThreadTaskRunnerImpl.java",
-        "base/android/java/src/org/chromium/base/task/TaskExecutor.java",
-        "base/android/java/src/org/chromium/base/task/TaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java",
-        "base/android/java/src/org/chromium/base/task/TaskTraits.java",
-        "base/android/java/src/org/chromium/base/task/TaskTraitsExtensionDescriptor.java",
-        "build/android/java/src/org/chromium/build/annotations/AlwaysInline.java",
-        "build/android/java/src/org/chromium/build/annotations/CheckDiscard.java",
-        "build/android/java/src/org/chromium/build/annotations/DoNotClassMerge.java",
-        "build/android/java/src/org/chromium/build/annotations/DoNotInline.java",
-        "build/android/java/src/org/chromium/build/annotations/IdentifierNameString.java",
-        "build/android/java/src/org/chromium/build/annotations/MainDex.java",
-        "build/android/java/src/org/chromium/build/annotations/MockedInTests.java",
-        "build/android/java/src/org/chromium/build/annotations/UsedByReflection.java",
-        "components/cronet/android/api/src/org/chromium/net/BidirectionalStream.java",
-        "components/cronet/android/api/src/org/chromium/net/CallbackException.java",
-        "components/cronet/android/api/src/org/chromium/net/CronetEngine.java",
-        "components/cronet/android/api/src/org/chromium/net/CronetException.java",
-        "components/cronet/android/api/src/org/chromium/net/CronetProvider.java",
-        "components/cronet/android/api/src/org/chromium/net/ExperimentalBidirectionalStream.java",
-        "components/cronet/android/api/src/org/chromium/net/ExperimentalCronetEngine.java",
-        "components/cronet/android/api/src/org/chromium/net/ExperimentalUrlRequest.java",
-        "components/cronet/android/api/src/org/chromium/net/ICronetEngineBuilder.java",
-        "components/cronet/android/api/src/org/chromium/net/InlineExecutionProhibitedException.java",
-        "components/cronet/android/api/src/org/chromium/net/NetworkException.java",
-        "components/cronet/android/api/src/org/chromium/net/NetworkQualityRttListener.java",
-        "components/cronet/android/api/src/org/chromium/net/NetworkQualityThroughputListener.java",
-        "components/cronet/android/api/src/org/chromium/net/QuicException.java",
-        "components/cronet/android/api/src/org/chromium/net/RequestFinishedInfo.java",
-        "components/cronet/android/api/src/org/chromium/net/UploadDataProvider.java",
-        "components/cronet/android/api/src/org/chromium/net/UploadDataProviders.java",
-        "components/cronet/android/api/src/org/chromium/net/UploadDataSink.java",
-        "components/cronet/android/api/src/org/chromium/net/UrlRequest.java",
-        "components/cronet/android/api/src/org/chromium/net/UrlResponseInfo.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/ByteArrayCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/ContentTypeParametersParser.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/CronetRequestCompletionListener.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/CronetResponse.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/ImplicitFlowControlCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/InMemoryTransformCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/JsonCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/RedirectHandler.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/RedirectHandlers.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/StringCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/UploadDataProviders.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/UrlRequestCallbacks.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetController.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetEngine.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetProvider.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeUrlRequest.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeUrlResponse.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/ResponseMatcher.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/UrlResponseMatcher.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/BidirectionalStreamBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/BidirectionalStreamNetworkException.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CallbackExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBase.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLibraryLoader.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLogger.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLoggerFactory.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetManifest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetMetrics.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/InputStreamChannel.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngine.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngineBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetProvider.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaUploadDataSinkBase.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequestUtils.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderWithLibraryLoaderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetProvider.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NoOpLogger.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/Preconditions.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/QuicExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/RequestFinishedInfoImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UrlRequestBase.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UrlRequestBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UrlResponseInfoImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UserAgent.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/VersionSafeCallbacks.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetBufferedOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetChunkedOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetFixedModeOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLConnection.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLStreamHandler.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetInputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetURLStreamHandlerFactory.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/MessageLoop.java",
-        "net/android/java/src/org/chromium/net/AndroidCertVerifyResult.java",
-        "net/android/java/src/org/chromium/net/AndroidKeyStore.java",
-        "net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java",
-        "net/android/java/src/org/chromium/net/AndroidTrafficStats.java",
-        "net/android/java/src/org/chromium/net/ChromiumNetworkAdapter.java",
-        "net/android/java/src/org/chromium/net/DnsStatus.java",
-        "net/android/java/src/org/chromium/net/GURLUtils.java",
-        "net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java",
-        "net/android/java/src/org/chromium/net/HttpNegotiateConstants.java",
-        "net/android/java/src/org/chromium/net/HttpUtil.java",
-        "net/android/java/src/org/chromium/net/MimeTypeFilter.java",
-        "net/android/java/src/org/chromium/net/NetStringUtil.java",
-        "net/android/java/src/org/chromium/net/NetworkActiveNotifier.java",
-        "net/android/java/src/org/chromium/net/NetworkChangeNotifier.java",
-        "net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java",
-        "net/android/java/src/org/chromium/net/NetworkTrafficAnnotationTag.java",
-        "net/android/java/src/org/chromium/net/ProxyBroadcastReceiver.java",
-        "net/android/java/src/org/chromium/net/ProxyChangeListener.java",
-        "net/android/java/src/org/chromium/net/RegistrationPolicyAlwaysRegister.java",
-        "net/android/java/src/org/chromium/net/RegistrationPolicyApplicationStatus.java",
-        "net/android/java/src/org/chromium/net/ThreadStatsUid.java",
-        "net/android/java/src/org/chromium/net/X509Util.java",
-        "url/android/java/src/org/chromium/url/IDNStringUtil.java",
-    ],
-    cmd: "current_dir=`basename \\`pwd\\``; " +
-         "for f in $(in); " +
-         "do " +
-         "echo \"../$$current_dir/$$f\" >> $(genDir)/java.sources; " +
-         "done; " +
-         "python3 $(location base/android/jni_generator/jni_registration_generator.py) --srcjar-path " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.srcjar " +
-         "--depfile " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.d " +
-         "--sources-files " +
-         "$(genDir)/java.sources " +
-         "--include_test_only " +
-         "--use_proxy_hash " +
-         "--header-path " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.h " +
-         "--manual_jni_registration " +
-         ";sed -i -e 's/OUT_SOONG_.TEMP_SBOX_.*_OUT/GEN/g'  " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.h",
-    out: [
-        "components/cronet/android/cronet_jni_registration.h",
-        "components/cronet/android/cronet_jni_registration.srcjar",
-    ],
-    tool_files: [
-        "base/android/jni_generator/jni_generator.py",
-        "base/android/jni_generator/jni_registration_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet/android:cronet_jni_registration__android_x86_64
-cc_genrule {
-    name: "cronet_aml_components_cronet_android_cronet_jni_registration__android_x86_64",
-    srcs: [
-        "base/android/java/src/org/chromium/base/ActivityState.java",
-        "base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java",
-        "base/android/java/src/org/chromium/base/ApkAssets.java",
-        "base/android/java/src/org/chromium/base/ApplicationStatus.java",
-        "base/android/java/src/org/chromium/base/BaseFeatureList.java",
-        "base/android/java/src/org/chromium/base/BuildInfo.java",
-        "base/android/java/src/org/chromium/base/BundleUtils.java",
-        "base/android/java/src/org/chromium/base/ByteArrayGenerator.java",
-        "base/android/java/src/org/chromium/base/Callback.java",
-        "base/android/java/src/org/chromium/base/CallbackController.java",
-        "base/android/java/src/org/chromium/base/CollectionUtil.java",
-        "base/android/java/src/org/chromium/base/CommandLine.java",
-        "base/android/java/src/org/chromium/base/CommandLineInitUtil.java",
-        "base/android/java/src/org/chromium/base/Consumer.java",
-        "base/android/java/src/org/chromium/base/ContentUriUtils.java",
-        "base/android/java/src/org/chromium/base/ContextUtils.java",
-        "base/android/java/src/org/chromium/base/CpuFeatures.java",
-        "base/android/java/src/org/chromium/base/DiscardableReferencePool.java",
-        "base/android/java/src/org/chromium/base/EarlyTraceEvent.java",
-        "base/android/java/src/org/chromium/base/EventLog.java",
-        "base/android/java/src/org/chromium/base/FeatureList.java",
-        "base/android/java/src/org/chromium/base/Features.java",
-        "base/android/java/src/org/chromium/base/FieldTrialList.java",
-        "base/android/java/src/org/chromium/base/FileUtils.java",
-        "base/android/java/src/org/chromium/base/Function.java",
-        "base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java",
-        "base/android/java/src/org/chromium/base/IntStringCallback.java",
-        "base/android/java/src/org/chromium/base/IntentUtils.java",
-        "base/android/java/src/org/chromium/base/JNIUtils.java",
-        "base/android/java/src/org/chromium/base/JavaExceptionReporter.java",
-        "base/android/java/src/org/chromium/base/JavaHandlerThread.java",
-        "base/android/java/src/org/chromium/base/JniException.java",
-        "base/android/java/src/org/chromium/base/JniStaticTestMocker.java",
-        "base/android/java/src/org/chromium/base/LifetimeAssert.java",
-        "base/android/java/src/org/chromium/base/LocaleUtils.java",
-        "base/android/java/src/org/chromium/base/Log.java",
-        "base/android/java/src/org/chromium/base/MathUtils.java",
-        "base/android/java/src/org/chromium/base/MemoryPressureListener.java",
-        "base/android/java/src/org/chromium/base/NativeLibraryLoadedStatus.java",
-        "base/android/java/src/org/chromium/base/ObserverList.java",
-        "base/android/java/src/org/chromium/base/PackageManagerUtils.java",
-        "base/android/java/src/org/chromium/base/PackageUtils.java",
-        "base/android/java/src/org/chromium/base/PathService.java",
-        "base/android/java/src/org/chromium/base/PathUtils.java",
-        "base/android/java/src/org/chromium/base/PiiElider.java",
-        "base/android/java/src/org/chromium/base/PowerMonitor.java",
-        "base/android/java/src/org/chromium/base/PowerMonitorForQ.java",
-        "base/android/java/src/org/chromium/base/Predicate.java",
-        "base/android/java/src/org/chromium/base/Promise.java",
-        "base/android/java/src/org/chromium/base/RadioUtils.java",
-        "base/android/java/src/org/chromium/base/StreamUtil.java",
-        "base/android/java/src/org/chromium/base/StrictModeContext.java",
-        "base/android/java/src/org/chromium/base/ThreadUtils.java",
-        "base/android/java/src/org/chromium/base/TimeUtils.java",
-        "base/android/java/src/org/chromium/base/TimezoneUtils.java",
-        "base/android/java/src/org/chromium/base/TraceEvent.java",
-        "base/android/java/src/org/chromium/base/UnguessableToken.java",
-        "base/android/java/src/org/chromium/base/UnownedUserData.java",
-        "base/android/java/src/org/chromium/base/UnownedUserDataHost.java",
-        "base/android/java/src/org/chromium/base/UnownedUserDataKey.java",
-        "base/android/java/src/org/chromium/base/UserData.java",
-        "base/android/java/src/org/chromium/base/UserDataHost.java",
-        "base/android/java/src/org/chromium/base/WrappedClassLoader.java",
-        "base/android/java/src/org/chromium/base/annotations/AccessedByNative.java",
-        "base/android/java/src/org/chromium/base/annotations/CalledByNative.java",
-        "base/android/java/src/org/chromium/base/annotations/CalledByNativeForTesting.java",
-        "base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java",
-        "base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java",
-        "base/android/java/src/org/chromium/base/annotations/JNINamespace.java",
-        "base/android/java/src/org/chromium/base/annotations/JniIgnoreNatives.java",
-        "base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java",
-        "base/android/java/src/org/chromium/base/annotations/NativeMethods.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForM.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForN.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForO.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForOMR1.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForP.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForQ.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForR.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForS.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/DummyJankTracker.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/FrameMetrics.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsListener.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsStore.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankActivityTracker.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetricCalculator.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetrics.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankReportingRunnable.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankReportingScheduler.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankScenario.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankTracker.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankTrackerImpl.java",
-        "base/android/java/src/org/chromium/base/library_loader/LegacyLinker.java",
-        "base/android/java/src/org/chromium/base/library_loader/Linker.java",
-        "base/android/java/src/org/chromium/base/library_loader/LinkerJni.java",
-        "base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java",
-        "base/android/java/src/org/chromium/base/library_loader/ModernLinker.java",
-        "base/android/java/src/org/chromium/base/library_loader/ModernLinkerJni.java",
-        "base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java",
-        "base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java",
-        "base/android/java/src/org/chromium/base/lifetime/DestroyChecker.java",
-        "base/android/java/src/org/chromium/base/lifetime/Destroyable.java",
-        "base/android/java/src/org/chromium/base/memory/JavaHeapDumpGenerator.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPurgeManager.java",
-        "base/android/java/src/org/chromium/base/metrics/CachingUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/NativeUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/NoopUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/RecordHistogram.java",
-        "base/android/java/src/org/chromium/base/metrics/RecordUserAction.java",
-        "base/android/java/src/org/chromium/base/metrics/ScopedSysTraceEvent.java",
-        "base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java",
-        "base/android/java/src/org/chromium/base/metrics/TimingMetric.java",
-        "base/android/java/src/org/chromium/base/metrics/UmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/UmaRecorderHolder.java",
-        "base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java",
-        "base/android/java/src/org/chromium/base/process_launcher/BindService.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnection.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionDelegate.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionFactory.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionImpl.java",
-        "base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java",
-        "base/android/java/src/org/chromium/base/supplier/BooleanSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/DestroyableObservableSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/ObservableSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/ObservableSupplierImpl.java",
-        "base/android/java/src/org/chromium/base/supplier/OneShotCallback.java",
-        "base/android/java/src/org/chromium/base/supplier/OneshotSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/OneshotSupplierImpl.java",
-        "base/android/java/src/org/chromium/base/supplier/Supplier.java",
-        "base/android/java/src/org/chromium/base/supplier/UnownedUserDataSupplier.java",
-        "base/android/java/src/org/chromium/base/task/AsyncTask.java",
-        "base/android/java/src/org/chromium/base/task/BackgroundOnlyAsyncTask.java",
-        "base/android/java/src/org/chromium/base/task/ChainedTasks.java",
-        "base/android/java/src/org/chromium/base/task/ChoreographerTaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/ChromeThreadPoolExecutor.java",
-        "base/android/java/src/org/chromium/base/task/DefaultTaskExecutor.java",
-        "base/android/java/src/org/chromium/base/task/PostTask.java",
-        "base/android/java/src/org/chromium/base/task/SequencedTaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/SequencedTaskRunnerImpl.java",
-        "base/android/java/src/org/chromium/base/task/SerialExecutor.java",
-        "base/android/java/src/org/chromium/base/task/SingleThreadTaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/SingleThreadTaskRunnerImpl.java",
-        "base/android/java/src/org/chromium/base/task/TaskExecutor.java",
-        "base/android/java/src/org/chromium/base/task/TaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java",
-        "base/android/java/src/org/chromium/base/task/TaskTraits.java",
-        "base/android/java/src/org/chromium/base/task/TaskTraitsExtensionDescriptor.java",
-        "build/android/java/src/org/chromium/build/annotations/AlwaysInline.java",
-        "build/android/java/src/org/chromium/build/annotations/CheckDiscard.java",
-        "build/android/java/src/org/chromium/build/annotations/DoNotClassMerge.java",
-        "build/android/java/src/org/chromium/build/annotations/DoNotInline.java",
-        "build/android/java/src/org/chromium/build/annotations/IdentifierNameString.java",
-        "build/android/java/src/org/chromium/build/annotations/MainDex.java",
-        "build/android/java/src/org/chromium/build/annotations/MockedInTests.java",
-        "build/android/java/src/org/chromium/build/annotations/UsedByReflection.java",
-        "components/cronet/android/api/src/org/chromium/net/BidirectionalStream.java",
-        "components/cronet/android/api/src/org/chromium/net/CallbackException.java",
-        "components/cronet/android/api/src/org/chromium/net/CronetEngine.java",
-        "components/cronet/android/api/src/org/chromium/net/CronetException.java",
-        "components/cronet/android/api/src/org/chromium/net/CronetProvider.java",
-        "components/cronet/android/api/src/org/chromium/net/ExperimentalBidirectionalStream.java",
-        "components/cronet/android/api/src/org/chromium/net/ExperimentalCronetEngine.java",
-        "components/cronet/android/api/src/org/chromium/net/ExperimentalUrlRequest.java",
-        "components/cronet/android/api/src/org/chromium/net/ICronetEngineBuilder.java",
-        "components/cronet/android/api/src/org/chromium/net/InlineExecutionProhibitedException.java",
-        "components/cronet/android/api/src/org/chromium/net/NetworkException.java",
-        "components/cronet/android/api/src/org/chromium/net/NetworkQualityRttListener.java",
-        "components/cronet/android/api/src/org/chromium/net/NetworkQualityThroughputListener.java",
-        "components/cronet/android/api/src/org/chromium/net/QuicException.java",
-        "components/cronet/android/api/src/org/chromium/net/RequestFinishedInfo.java",
-        "components/cronet/android/api/src/org/chromium/net/UploadDataProvider.java",
-        "components/cronet/android/api/src/org/chromium/net/UploadDataProviders.java",
-        "components/cronet/android/api/src/org/chromium/net/UploadDataSink.java",
-        "components/cronet/android/api/src/org/chromium/net/UrlRequest.java",
-        "components/cronet/android/api/src/org/chromium/net/UrlResponseInfo.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/ByteArrayCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/ContentTypeParametersParser.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/CronetRequestCompletionListener.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/CronetResponse.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/ImplicitFlowControlCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/InMemoryTransformCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/JsonCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/RedirectHandler.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/RedirectHandlers.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/StringCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/UploadDataProviders.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/UrlRequestCallbacks.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetController.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetEngine.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetProvider.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeUrlRequest.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeUrlResponse.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/ResponseMatcher.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/UrlResponseMatcher.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/BidirectionalStreamBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/BidirectionalStreamNetworkException.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CallbackExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBase.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLibraryLoader.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLogger.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLoggerFactory.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetManifest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetMetrics.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/InputStreamChannel.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngine.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngineBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetProvider.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaUploadDataSinkBase.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequestUtils.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderWithLibraryLoaderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetProvider.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NoOpLogger.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/Preconditions.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/QuicExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/RequestFinishedInfoImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UrlRequestBase.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UrlRequestBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UrlResponseInfoImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UserAgent.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/VersionSafeCallbacks.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetBufferedOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetChunkedOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetFixedModeOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLConnection.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLStreamHandler.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetInputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetURLStreamHandlerFactory.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/MessageLoop.java",
-        "net/android/java/src/org/chromium/net/AndroidCertVerifyResult.java",
-        "net/android/java/src/org/chromium/net/AndroidKeyStore.java",
-        "net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java",
-        "net/android/java/src/org/chromium/net/AndroidTrafficStats.java",
-        "net/android/java/src/org/chromium/net/ChromiumNetworkAdapter.java",
-        "net/android/java/src/org/chromium/net/DnsStatus.java",
-        "net/android/java/src/org/chromium/net/GURLUtils.java",
-        "net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java",
-        "net/android/java/src/org/chromium/net/HttpNegotiateConstants.java",
-        "net/android/java/src/org/chromium/net/HttpUtil.java",
-        "net/android/java/src/org/chromium/net/MimeTypeFilter.java",
-        "net/android/java/src/org/chromium/net/NetStringUtil.java",
-        "net/android/java/src/org/chromium/net/NetworkActiveNotifier.java",
-        "net/android/java/src/org/chromium/net/NetworkChangeNotifier.java",
-        "net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java",
-        "net/android/java/src/org/chromium/net/NetworkTrafficAnnotationTag.java",
-        "net/android/java/src/org/chromium/net/ProxyBroadcastReceiver.java",
-        "net/android/java/src/org/chromium/net/ProxyChangeListener.java",
-        "net/android/java/src/org/chromium/net/RegistrationPolicyAlwaysRegister.java",
-        "net/android/java/src/org/chromium/net/RegistrationPolicyApplicationStatus.java",
-        "net/android/java/src/org/chromium/net/ThreadStatsUid.java",
-        "net/android/java/src/org/chromium/net/X509Util.java",
-        "url/android/java/src/org/chromium/url/IDNStringUtil.java",
-    ],
-    cmd: "current_dir=`basename \\`pwd\\``; " +
-         "for f in $(in); " +
-         "do " +
-         "echo \"../$$current_dir/$$f\" >> $(genDir)/java.sources; " +
-         "done; " +
-         "python3 $(location base/android/jni_generator/jni_registration_generator.py) --srcjar-path " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.srcjar " +
-         "--depfile " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.d " +
-         "--sources-files " +
-         "$(genDir)/java.sources " +
-         "--include_test_only " +
-         "--use_proxy_hash " +
-         "--header-path " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.h " +
-         "--manual_jni_registration " +
-         ";sed -i -e 's/OUT_SOONG_.TEMP_SBOX_.*_OUT/GEN/g'  " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.h",
-    out: [
-        "components/cronet/android/cronet_jni_registration.h",
-        "components/cronet/android/cronet_jni_registration.srcjar",
-    ],
-    tool_files: [
-        "base/android/jni_generator/jni_generator.py",
-        "base/android/jni_generator/jni_registration_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet/android:cronet_jni_registration
-java_genrule {
-    name: "cronet_aml_components_cronet_android_cronet_jni_registration__java",
-    srcs: [
-        "base/android/java/src/org/chromium/base/ActivityState.java",
-        "base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java",
-        "base/android/java/src/org/chromium/base/ApkAssets.java",
-        "base/android/java/src/org/chromium/base/ApplicationStatus.java",
-        "base/android/java/src/org/chromium/base/BaseFeatureList.java",
-        "base/android/java/src/org/chromium/base/BuildInfo.java",
-        "base/android/java/src/org/chromium/base/BundleUtils.java",
-        "base/android/java/src/org/chromium/base/ByteArrayGenerator.java",
-        "base/android/java/src/org/chromium/base/Callback.java",
-        "base/android/java/src/org/chromium/base/CallbackController.java",
-        "base/android/java/src/org/chromium/base/CollectionUtil.java",
-        "base/android/java/src/org/chromium/base/CommandLine.java",
-        "base/android/java/src/org/chromium/base/CommandLineInitUtil.java",
-        "base/android/java/src/org/chromium/base/Consumer.java",
-        "base/android/java/src/org/chromium/base/ContentUriUtils.java",
-        "base/android/java/src/org/chromium/base/ContextUtils.java",
-        "base/android/java/src/org/chromium/base/CpuFeatures.java",
-        "base/android/java/src/org/chromium/base/DiscardableReferencePool.java",
-        "base/android/java/src/org/chromium/base/EarlyTraceEvent.java",
-        "base/android/java/src/org/chromium/base/EventLog.java",
-        "base/android/java/src/org/chromium/base/FeatureList.java",
-        "base/android/java/src/org/chromium/base/Features.java",
-        "base/android/java/src/org/chromium/base/FieldTrialList.java",
-        "base/android/java/src/org/chromium/base/FileUtils.java",
-        "base/android/java/src/org/chromium/base/Function.java",
-        "base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java",
-        "base/android/java/src/org/chromium/base/IntStringCallback.java",
-        "base/android/java/src/org/chromium/base/IntentUtils.java",
-        "base/android/java/src/org/chromium/base/JNIUtils.java",
-        "base/android/java/src/org/chromium/base/JavaExceptionReporter.java",
-        "base/android/java/src/org/chromium/base/JavaHandlerThread.java",
-        "base/android/java/src/org/chromium/base/JniException.java",
-        "base/android/java/src/org/chromium/base/JniStaticTestMocker.java",
-        "base/android/java/src/org/chromium/base/LifetimeAssert.java",
-        "base/android/java/src/org/chromium/base/LocaleUtils.java",
-        "base/android/java/src/org/chromium/base/Log.java",
-        "base/android/java/src/org/chromium/base/MathUtils.java",
-        "base/android/java/src/org/chromium/base/MemoryPressureListener.java",
-        "base/android/java/src/org/chromium/base/NativeLibraryLoadedStatus.java",
-        "base/android/java/src/org/chromium/base/ObserverList.java",
-        "base/android/java/src/org/chromium/base/PackageManagerUtils.java",
-        "base/android/java/src/org/chromium/base/PackageUtils.java",
-        "base/android/java/src/org/chromium/base/PathService.java",
-        "base/android/java/src/org/chromium/base/PathUtils.java",
-        "base/android/java/src/org/chromium/base/PiiElider.java",
-        "base/android/java/src/org/chromium/base/PowerMonitor.java",
-        "base/android/java/src/org/chromium/base/PowerMonitorForQ.java",
-        "base/android/java/src/org/chromium/base/Predicate.java",
-        "base/android/java/src/org/chromium/base/Promise.java",
-        "base/android/java/src/org/chromium/base/RadioUtils.java",
-        "base/android/java/src/org/chromium/base/StreamUtil.java",
-        "base/android/java/src/org/chromium/base/StrictModeContext.java",
-        "base/android/java/src/org/chromium/base/ThreadUtils.java",
-        "base/android/java/src/org/chromium/base/TimeUtils.java",
-        "base/android/java/src/org/chromium/base/TimezoneUtils.java",
-        "base/android/java/src/org/chromium/base/TraceEvent.java",
-        "base/android/java/src/org/chromium/base/UnguessableToken.java",
-        "base/android/java/src/org/chromium/base/UnownedUserData.java",
-        "base/android/java/src/org/chromium/base/UnownedUserDataHost.java",
-        "base/android/java/src/org/chromium/base/UnownedUserDataKey.java",
-        "base/android/java/src/org/chromium/base/UserData.java",
-        "base/android/java/src/org/chromium/base/UserDataHost.java",
-        "base/android/java/src/org/chromium/base/WrappedClassLoader.java",
-        "base/android/java/src/org/chromium/base/annotations/AccessedByNative.java",
-        "base/android/java/src/org/chromium/base/annotations/CalledByNative.java",
-        "base/android/java/src/org/chromium/base/annotations/CalledByNativeForTesting.java",
-        "base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java",
-        "base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java",
-        "base/android/java/src/org/chromium/base/annotations/JNINamespace.java",
-        "base/android/java/src/org/chromium/base/annotations/JniIgnoreNatives.java",
-        "base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java",
-        "base/android/java/src/org/chromium/base/annotations/NativeMethods.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForM.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForN.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForO.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForOMR1.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForP.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForQ.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForR.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForS.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/DummyJankTracker.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/FrameMetrics.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsListener.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsStore.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankActivityTracker.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetricCalculator.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetrics.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankReportingRunnable.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankReportingScheduler.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankScenario.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankTracker.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankTrackerImpl.java",
-        "base/android/java/src/org/chromium/base/library_loader/LegacyLinker.java",
-        "base/android/java/src/org/chromium/base/library_loader/Linker.java",
-        "base/android/java/src/org/chromium/base/library_loader/LinkerJni.java",
-        "base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java",
-        "base/android/java/src/org/chromium/base/library_loader/ModernLinker.java",
-        "base/android/java/src/org/chromium/base/library_loader/ModernLinkerJni.java",
-        "base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java",
-        "base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java",
-        "base/android/java/src/org/chromium/base/lifetime/DestroyChecker.java",
-        "base/android/java/src/org/chromium/base/lifetime/Destroyable.java",
-        "base/android/java/src/org/chromium/base/memory/JavaHeapDumpGenerator.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPurgeManager.java",
-        "base/android/java/src/org/chromium/base/metrics/CachingUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/NativeUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/NoopUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/RecordHistogram.java",
-        "base/android/java/src/org/chromium/base/metrics/RecordUserAction.java",
-        "base/android/java/src/org/chromium/base/metrics/ScopedSysTraceEvent.java",
-        "base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java",
-        "base/android/java/src/org/chromium/base/metrics/TimingMetric.java",
-        "base/android/java/src/org/chromium/base/metrics/UmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/UmaRecorderHolder.java",
-        "base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java",
-        "base/android/java/src/org/chromium/base/process_launcher/BindService.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnection.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionDelegate.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionFactory.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionImpl.java",
-        "base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java",
-        "base/android/java/src/org/chromium/base/supplier/BooleanSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/DestroyableObservableSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/ObservableSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/ObservableSupplierImpl.java",
-        "base/android/java/src/org/chromium/base/supplier/OneShotCallback.java",
-        "base/android/java/src/org/chromium/base/supplier/OneshotSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/OneshotSupplierImpl.java",
-        "base/android/java/src/org/chromium/base/supplier/Supplier.java",
-        "base/android/java/src/org/chromium/base/supplier/UnownedUserDataSupplier.java",
-        "base/android/java/src/org/chromium/base/task/AsyncTask.java",
-        "base/android/java/src/org/chromium/base/task/BackgroundOnlyAsyncTask.java",
-        "base/android/java/src/org/chromium/base/task/ChainedTasks.java",
-        "base/android/java/src/org/chromium/base/task/ChoreographerTaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/ChromeThreadPoolExecutor.java",
-        "base/android/java/src/org/chromium/base/task/DefaultTaskExecutor.java",
-        "base/android/java/src/org/chromium/base/task/PostTask.java",
-        "base/android/java/src/org/chromium/base/task/SequencedTaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/SequencedTaskRunnerImpl.java",
-        "base/android/java/src/org/chromium/base/task/SerialExecutor.java",
-        "base/android/java/src/org/chromium/base/task/SingleThreadTaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/SingleThreadTaskRunnerImpl.java",
-        "base/android/java/src/org/chromium/base/task/TaskExecutor.java",
-        "base/android/java/src/org/chromium/base/task/TaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java",
-        "base/android/java/src/org/chromium/base/task/TaskTraits.java",
-        "base/android/java/src/org/chromium/base/task/TaskTraitsExtensionDescriptor.java",
-        "build/android/java/src/org/chromium/build/annotations/AlwaysInline.java",
-        "build/android/java/src/org/chromium/build/annotations/CheckDiscard.java",
-        "build/android/java/src/org/chromium/build/annotations/DoNotClassMerge.java",
-        "build/android/java/src/org/chromium/build/annotations/DoNotInline.java",
-        "build/android/java/src/org/chromium/build/annotations/IdentifierNameString.java",
-        "build/android/java/src/org/chromium/build/annotations/MainDex.java",
-        "build/android/java/src/org/chromium/build/annotations/MockedInTests.java",
-        "build/android/java/src/org/chromium/build/annotations/UsedByReflection.java",
-        "components/cronet/android/api/src/org/chromium/net/BidirectionalStream.java",
-        "components/cronet/android/api/src/org/chromium/net/CallbackException.java",
-        "components/cronet/android/api/src/org/chromium/net/CronetEngine.java",
-        "components/cronet/android/api/src/org/chromium/net/CronetException.java",
-        "components/cronet/android/api/src/org/chromium/net/CronetProvider.java",
-        "components/cronet/android/api/src/org/chromium/net/ExperimentalBidirectionalStream.java",
-        "components/cronet/android/api/src/org/chromium/net/ExperimentalCronetEngine.java",
-        "components/cronet/android/api/src/org/chromium/net/ExperimentalUrlRequest.java",
-        "components/cronet/android/api/src/org/chromium/net/ICronetEngineBuilder.java",
-        "components/cronet/android/api/src/org/chromium/net/InlineExecutionProhibitedException.java",
-        "components/cronet/android/api/src/org/chromium/net/NetworkException.java",
-        "components/cronet/android/api/src/org/chromium/net/NetworkQualityRttListener.java",
-        "components/cronet/android/api/src/org/chromium/net/NetworkQualityThroughputListener.java",
-        "components/cronet/android/api/src/org/chromium/net/QuicException.java",
-        "components/cronet/android/api/src/org/chromium/net/RequestFinishedInfo.java",
-        "components/cronet/android/api/src/org/chromium/net/UploadDataProvider.java",
-        "components/cronet/android/api/src/org/chromium/net/UploadDataProviders.java",
-        "components/cronet/android/api/src/org/chromium/net/UploadDataSink.java",
-        "components/cronet/android/api/src/org/chromium/net/UrlRequest.java",
-        "components/cronet/android/api/src/org/chromium/net/UrlResponseInfo.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/ByteArrayCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/ContentTypeParametersParser.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/CronetRequestCompletionListener.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/CronetResponse.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/ImplicitFlowControlCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/InMemoryTransformCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/JsonCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/RedirectHandler.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/RedirectHandlers.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/StringCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/UploadDataProviders.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/UrlRequestCallbacks.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetController.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetEngine.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetProvider.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeUrlRequest.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeUrlResponse.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/ResponseMatcher.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/UrlResponseMatcher.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/BidirectionalStreamBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/BidirectionalStreamNetworkException.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CallbackExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBase.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLibraryLoader.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLogger.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLoggerFactory.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetManifest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetMetrics.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/InputStreamChannel.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngine.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngineBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetProvider.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaUploadDataSinkBase.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequestUtils.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderWithLibraryLoaderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetProvider.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NoOpLogger.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/Preconditions.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/QuicExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/RequestFinishedInfoImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UrlRequestBase.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UrlRequestBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UrlResponseInfoImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UserAgent.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/VersionSafeCallbacks.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetBufferedOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetChunkedOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetFixedModeOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLConnection.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLStreamHandler.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetInputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetURLStreamHandlerFactory.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/MessageLoop.java",
-        "net/android/java/src/org/chromium/net/AndroidCertVerifyResult.java",
-        "net/android/java/src/org/chromium/net/AndroidKeyStore.java",
-        "net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java",
-        "net/android/java/src/org/chromium/net/AndroidTrafficStats.java",
-        "net/android/java/src/org/chromium/net/ChromiumNetworkAdapter.java",
-        "net/android/java/src/org/chromium/net/DnsStatus.java",
-        "net/android/java/src/org/chromium/net/GURLUtils.java",
-        "net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java",
-        "net/android/java/src/org/chromium/net/HttpNegotiateConstants.java",
-        "net/android/java/src/org/chromium/net/HttpUtil.java",
-        "net/android/java/src/org/chromium/net/MimeTypeFilter.java",
-        "net/android/java/src/org/chromium/net/NetStringUtil.java",
-        "net/android/java/src/org/chromium/net/NetworkActiveNotifier.java",
-        "net/android/java/src/org/chromium/net/NetworkChangeNotifier.java",
-        "net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java",
-        "net/android/java/src/org/chromium/net/NetworkTrafficAnnotationTag.java",
-        "net/android/java/src/org/chromium/net/ProxyBroadcastReceiver.java",
-        "net/android/java/src/org/chromium/net/ProxyChangeListener.java",
-        "net/android/java/src/org/chromium/net/RegistrationPolicyAlwaysRegister.java",
-        "net/android/java/src/org/chromium/net/RegistrationPolicyApplicationStatus.java",
-        "net/android/java/src/org/chromium/net/ThreadStatsUid.java",
-        "net/android/java/src/org/chromium/net/X509Util.java",
-        "url/android/java/src/org/chromium/url/IDNStringUtil.java",
-    ],
-    cmd: "current_dir=`basename \\`pwd\\``; " +
-         "for f in $(in); " +
-         "do " +
-         "echo \"../$$current_dir/$$f\" >> $(genDir)/java.sources; " +
-         "done; " +
-         "python3 $(location base/android/jni_generator/jni_registration_generator.py) --srcjar-path " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.srcjar " +
-         "--depfile " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.d " +
-         "--sources-files " +
-         "$(genDir)/java.sources " +
-         "--include_test_only " +
-         "--use_proxy_hash " +
-         "--header-path " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.h " +
-         "--manual_jni_registration " +
-         ";sed -i -e 's/OUT_SOONG_.TEMP_SBOX_.*_OUT/GEN/g'  " +
-         "$(genDir)/components/cronet/android/cronet_jni_registration.h",
-    out: [
-        "components/cronet/android/cronet_jni_registration.srcjar",
-    ],
-    tool_files: [
-        "base/android/jni_generator/jni_generator.py",
-        "base/android/jni_generator/jni_registration_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-}
-
-// GN: //components/cronet/android:cronet_static
-cc_object {
-    name: "cronet_aml_components_cronet_android_cronet_static",
-    srcs: [
-        "components/cronet/android/cronet_bidirectional_stream_adapter.cc",
-        "components/cronet/android/cronet_context_adapter.cc",
-        "components/cronet/android/cronet_library_loader.cc",
-        "components/cronet/android/cronet_upload_data_stream_adapter.cc",
-        "components/cronet/android/cronet_url_request_adapter.cc",
-        "components/cronet/android/io_buffer_with_byte_buffer.cc",
-        "components/cronet/android/url_request_error.cc",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_components_prefs_prefs",
-        "cronet_aml_crypto_crypto",
-        "cronet_aml_net_net",
-        "cronet_aml_net_preload_decoder",
-        "cronet_aml_net_third_party_quiche_quiche",
-        "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_brotli_common",
-        "cronet_aml_third_party_brotli_dec",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-        "cronet_aml_third_party_protobuf_protobuf_lite",
-        "cronet_aml_third_party_zlib_zlib",
-        "cronet_aml_url_url",
-    ],
-    generated_headers: [
-        "cronet_aml_third_party_metrics_proto_metrics_proto_gen_headers",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "components/cronet/native/generated/",
-        "components/cronet/native/include/",
-        "components/grpc_support/include/",
-        "net/third_party/quiche/overrides/",
-        "net/third_party/quiche/src/",
-        "net/third_party/quiche/src/quiche/common/platform/default/",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-        "third_party/protobuf/src/",
-        "third_party/zlib/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_components_cronet_android_buildflags__android_arm",
-                "cronet_aml_components_cronet_android_cronet_jni_headers__android_arm",
-                "cronet_aml_components_cronet_android_cronet_jni_registration__android_arm",
-                "cronet_aml_components_cronet_cronet_buildflags__android_arm",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_arm",
-                "cronet_aml_url_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_components_cronet_android_buildflags__android_arm64",
-                "cronet_aml_components_cronet_android_cronet_jni_headers__android_arm64",
-                "cronet_aml_components_cronet_android_cronet_jni_registration__android_arm64",
-                "cronet_aml_components_cronet_cronet_buildflags__android_arm64",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_arm64",
-                "cronet_aml_url_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_components_cronet_android_buildflags__android_x86",
-                "cronet_aml_components_cronet_android_cronet_jni_headers__android_x86",
-                "cronet_aml_components_cronet_android_cronet_jni_registration__android_x86",
-                "cronet_aml_components_cronet_cronet_buildflags__android_x86",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_x86",
-                "cronet_aml_url_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_components_cronet_android_buildflags__android_x86_64",
-                "cronet_aml_components_cronet_android_cronet_jni_headers__android_x86_64",
-                "cronet_aml_components_cronet_android_cronet_jni_registration__android_x86_64",
-                "cronet_aml_components_cronet_cronet_buildflags__android_x86_64",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_x86_64",
-                "cronet_aml_url_buildflags__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //components/cronet/android:http_cache_type_java
-java_genrule {
-    name: "cronet_aml_components_cronet_android_http_cache_type_java",
-    cmd: "$(location build/android/gyp/java_cpp_enum.py) --srcjar " +
-         "$(out) " +
-         "$(location components/cronet/url_request_context_config.h)",
-    out: [
-        "components/cronet/android/http_cache_type_java.srcjar",
-    ],
-    tool_files: [
-        "build/android/gyp/java_cpp_enum.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/android/gyp/util/java_cpp_utils.py",
-        "build/gn_helpers.py",
-        "components/cronet/url_request_context_config.h",
-    ],
-}
-
-// GN: //components/cronet/android:implementation_api_version
-java_genrule {
-    name: "cronet_aml_components_cronet_android_implementation_api_version",
-    cmd: "$(location build/util/version.py) -f " +
-         "$(location chrome/VERSION) " +
-         "-f " +
-         "$(location build/util/LASTCHANGE) " +
-         "-e " +
-         "'API_LEVEL=20' " +
-         "-o " +
-         "$(out) " +
-         "$(location components/cronet/android/java/src/org/chromium/net/impl/ImplVersion.template)",
-    out: [
-        "components/cronet/android/templates/org/chromium/net/impl/ImplVersion.java",
-    ],
-    tool_files: [
-        "build/util/LASTCHANGE",
-        "build/util/android_chrome_version.py",
-        "build/util/version.py",
-        "chrome/VERSION",
-        "components/cronet/android/java/src/org/chromium/net/impl/ImplVersion.template",
-    ],
-}
-
-// GN: //components/cronet/android:integrated_mode_state
-genrule {
-    name: "cronet_aml_components_cronet_android_integrated_mode_state",
-    srcs: [
-        ":cronet_aml_components_cronet_android_integrated_mode_state_preprocess",
-    ],
-    tools: [
-        "soong_zip",
-    ],
-    cmd: "cp $(in) $(genDir)/IntegratedModeState.java && " +
-         "$(location soong_zip) -o $(out) -srcjar -f $(genDir)/IntegratedModeState.java",
-    out: [
-        "IntegratedModeState.srcjar",
-    ],
-}
-
-// GN: //components/cronet/android:integrated_mode_state
-cc_object {
-    name: "cronet_aml_components_cronet_android_integrated_mode_state_preprocess",
-    srcs: [
-        ":cronet_aml_components_cronet_android_integrated_mode_state_rename",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-E",
-        "-P",
-    ],
-    compile_multilib: "first",
-}
-
-// GN: //components/cronet/android:integrated_mode_state
-genrule {
-    name: "cronet_aml_components_cronet_android_integrated_mode_state_rename",
-    srcs: [
-        "components/cronet/android/java/src/org/chromium/net/impl/IntegratedModeState.template",
-    ],
-    cmd: "cp $(in) $(out)",
-    out: [
-        "IntegratedModeState.cc",
-    ],
-}
-
-// GN: //components/cronet/android:interface_api_version
-java_genrule {
-    name: "cronet_aml_components_cronet_android_interface_api_version",
-    cmd: "$(location build/util/version.py) -f " +
-         "$(location chrome/VERSION) " +
-         "-f " +
-         "$(location build/util/LASTCHANGE) " +
-         "-e " +
-         "'API_LEVEL=20' " +
-         "-o " +
-         "$(out) " +
-         "$(location components/cronet/android/api/src/org/chromium/net/ApiVersion.template)",
-    out: [
-        "components/cronet/android/templates/org/chromium/net/ApiVersion.java",
-    ],
-    tool_files: [
-        "build/util/LASTCHANGE",
-        "build/util/android_chrome_version.py",
-        "build/util/version.py",
-        "chrome/VERSION",
-        "components/cronet/android/api/src/org/chromium/net/ApiVersion.template",
-    ],
-}
-
-// GN: //components/cronet/android:load_states_list
-genrule {
-    name: "cronet_aml_components_cronet_android_load_states_list",
-    srcs: [
-        ":cronet_aml_components_cronet_android_load_states_list_preprocess",
-    ],
-    tools: [
-        "soong_zip",
-    ],
-    cmd: "cp $(in) $(genDir)/LoadState.java && " +
-         "$(location soong_zip) -o $(out) -srcjar -f $(genDir)/LoadState.java",
-    out: [
-        "LoadState.srcjar",
-    ],
-}
-
-// GN: //components/cronet/android:load_states_list
-cc_object {
-    name: "cronet_aml_components_cronet_android_load_states_list_preprocess",
-    srcs: [
-        ":cronet_aml_components_cronet_android_load_states_list_rename",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-E",
-        "-P",
-    ],
-    compile_multilib: "first",
-}
-
-// GN: //components/cronet/android:load_states_list
-genrule {
-    name: "cronet_aml_components_cronet_android_load_states_list_rename",
-    srcs: [
-        "components/cronet/android/java/src/org/chromium/net/impl/LoadState.template",
-    ],
-    cmd: "cp $(in) $(out)",
-    out: [
-        "LoadState.cc",
-    ],
-}
-
-// GN: //components/cronet/android:net_idempotency_java
-java_genrule {
-    name: "cronet_aml_components_cronet_android_net_idempotency_java",
-    cmd: "$(location build/android/gyp/java_cpp_enum.py) --srcjar " +
-         "$(out) " +
-         "$(location net/base/idempotency.h)",
-    out: [
-        "components/cronet/android/net_idempotency_java.srcjar",
-    ],
-    tool_files: [
-        "build/android/gyp/java_cpp_enum.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/android/gyp/util/java_cpp_utils.py",
-        "build/gn_helpers.py",
-        "net/base/idempotency.h",
-    ],
-}
-
-// GN: //components/cronet/android:net_request_priority_java
-java_genrule {
-    name: "cronet_aml_components_cronet_android_net_request_priority_java",
-    cmd: "$(location build/android/gyp/java_cpp_enum.py) --srcjar " +
-         "$(out) " +
-         "$(location net/base/request_priority.h)",
-    out: [
-        "components/cronet/android/net_request_priority_java.srcjar",
-    ],
-    tool_files: [
-        "build/android/gyp/java_cpp_enum.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/android/gyp/util/java_cpp_utils.py",
-        "build/gn_helpers.py",
-        "net/base/request_priority.h",
-    ],
-}
-
-// GN: //components/cronet/android:network_quality_observation_source_java
-java_genrule {
-    name: "cronet_aml_components_cronet_android_network_quality_observation_source_java",
-    cmd: "$(location build/android/gyp/java_cpp_enum.py) --srcjar " +
-         "$(out) " +
-         "$(location net/nqe/network_quality_observation_source.h)",
-    out: [
-        "components/cronet/android/network_quality_observation_source_java.srcjar",
-    ],
-    tool_files: [
-        "build/android/gyp/java_cpp_enum.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/android/gyp/util/java_cpp_utils.py",
-        "build/gn_helpers.py",
-        "net/nqe/network_quality_observation_source.h",
-    ],
-}
-
-// GN: //components/cronet/android:rtt_throughput_values_java
-java_genrule {
-    name: "cronet_aml_components_cronet_android_rtt_throughput_values_java",
-    cmd: "$(location build/android/gyp/java_cpp_enum.py) --srcjar " +
-         "$(out) " +
-         "$(location net/nqe/network_quality.h)",
-    out: [
-        "components/cronet/android/rtt_throughput_values_java.srcjar",
-    ],
-    tool_files: [
-        "build/android/gyp/java_cpp_enum.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/android/gyp/util/java_cpp_utils.py",
-        "build/gn_helpers.py",
-        "net/nqe/network_quality.h",
-    ],
-}
-
-// GN: //components/cronet/android:url_request_error_java
-java_genrule {
-    name: "cronet_aml_components_cronet_android_url_request_error_java",
-    cmd: "$(location build/android/gyp/java_cpp_enum.py) --srcjar " +
-         "$(out) " +
-         "$(location components/cronet/android/url_request_error.h)",
-    out: [
-        "components/cronet/android/url_request_error_java.srcjar",
-    ],
-    tool_files: [
-        "build/android/gyp/java_cpp_enum.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/android/gyp/util/java_cpp_utils.py",
-        "build/gn_helpers.py",
-        "components/cronet/android/url_request_error.h",
-    ],
-}
-
-// GN: //components/cronet:cronet_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_components_cronet_cronet_buildflags__android_arm",
-    cmd: "echo '--flags DISABLE_HISTOGRAM_SUPPORT=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//components/cronet:cronet_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "components/cronet/cronet_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet:cronet_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_components_cronet_cronet_buildflags__android_arm64",
-    cmd: "echo '--flags DISABLE_HISTOGRAM_SUPPORT=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//components/cronet:cronet_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "components/cronet/cronet_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet:cronet_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_components_cronet_cronet_buildflags__android_x86",
-    cmd: "echo '--flags DISABLE_HISTOGRAM_SUPPORT=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//components/cronet:cronet_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "components/cronet/cronet_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet:cronet_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_components_cronet_cronet_buildflags__android_x86_64",
-    cmd: "echo '--flags DISABLE_HISTOGRAM_SUPPORT=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//components/cronet:cronet_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "components/cronet/cronet_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet:cronet_common
-cc_object {
-    name: "cronet_aml_components_cronet_cronet_common",
-    srcs: [
-        "components/cronet/cronet_context.cc",
-        "components/cronet/cronet_prefs_manager.cc",
-        "components/cronet/cronet_upload_data_stream.cc",
-        "components/cronet/cronet_url_request.cc",
-        "components/cronet/host_cache_persistence_manager.cc",
-        "components/cronet/stale_host_resolver.cc",
-        "components/cronet/url_request_context_config.cc",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_components_prefs_prefs",
-        "cronet_aml_crypto_crypto",
-        "cronet_aml_net_net",
-        "cronet_aml_net_preload_decoder",
-        "cronet_aml_net_third_party_quiche_quiche",
-        "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_brotli_common",
-        "cronet_aml_third_party_brotli_dec",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-        "cronet_aml_third_party_protobuf_protobuf_lite",
-        "cronet_aml_third_party_zlib_zlib",
-        "cronet_aml_url_url",
-    ],
-    generated_headers: [
-        "cronet_aml_third_party_metrics_proto_metrics_proto_gen_headers",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "net/third_party/quiche/overrides/",
-        "net/third_party/quiche/src/",
-        "net/third_party/quiche/src/quiche/common/platform/default/",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-        "third_party/protobuf/src/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            generated_headers: [
-                "cronet_aml_components_cronet_cronet_buildflags__android_arm",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_arm",
-            ],
-        },
-        android_arm64: {
-            generated_headers: [
-                "cronet_aml_components_cronet_cronet_buildflags__android_arm64",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_components_cronet_cronet_buildflags__android_x86",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_components_cronet_cronet_buildflags__android_x86_64",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //components/cronet:cronet_version_header
-cc_object {
-    name: "cronet_aml_components_cronet_cronet_version_header",
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            generated_headers: [
-                "cronet_aml_components_cronet_cronet_version_header_action__android_arm",
-            ],
-        },
-        android_arm64: {
-            generated_headers: [
-                "cronet_aml_components_cronet_cronet_version_header_action__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_components_cronet_cronet_version_header_action__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_components_cronet_cronet_version_header_action__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //components/cronet:cronet_version_header_action__android_arm
-cc_genrule {
-    name: "cronet_aml_components_cronet_cronet_version_header_action__android_arm",
-    cmd: "$(location build/util/version.py) -f " +
-         "$(location chrome/VERSION) " +
-         "-e " +
-         "'VERSION_FULL=\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH)' " +
-         "-o " +
-         "$(out) " +
-         "$(location components/cronet/version.h.in)",
-    out: [
-        "components/cronet/version.h",
-    ],
-    tool_files: [
-        "build/util/LASTCHANGE",
-        "build/util/android_chrome_version.py",
-        "build/util/version.py",
-        "chrome/VERSION",
-        "components/cronet/version.h.in",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet:cronet_version_header_action__android_arm64
-cc_genrule {
-    name: "cronet_aml_components_cronet_cronet_version_header_action__android_arm64",
-    cmd: "$(location build/util/version.py) -f " +
-         "$(location chrome/VERSION) " +
-         "-e " +
-         "'VERSION_FULL=\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH)' " +
-         "-o " +
-         "$(out) " +
-         "$(location components/cronet/version.h.in)",
-    out: [
-        "components/cronet/version.h",
-    ],
-    tool_files: [
-        "build/util/LASTCHANGE",
-        "build/util/android_chrome_version.py",
-        "build/util/version.py",
-        "chrome/VERSION",
-        "components/cronet/version.h.in",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet:cronet_version_header_action__android_x86
-cc_genrule {
-    name: "cronet_aml_components_cronet_cronet_version_header_action__android_x86",
-    cmd: "$(location build/util/version.py) -f " +
-         "$(location chrome/VERSION) " +
-         "-e " +
-         "'VERSION_FULL=\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH)' " +
-         "-o " +
-         "$(out) " +
-         "$(location components/cronet/version.h.in)",
-    out: [
-        "components/cronet/version.h",
-    ],
-    tool_files: [
-        "build/util/LASTCHANGE",
-        "build/util/android_chrome_version.py",
-        "build/util/version.py",
-        "chrome/VERSION",
-        "components/cronet/version.h.in",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet:cronet_version_header_action__android_x86_64
-cc_genrule {
-    name: "cronet_aml_components_cronet_cronet_version_header_action__android_x86_64",
-    cmd: "$(location build/util/version.py) -f " +
-         "$(location chrome/VERSION) " +
-         "-e " +
-         "'VERSION_FULL=\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH)' " +
-         "-o " +
-         "$(out) " +
-         "$(location components/cronet/version.h.in)",
-    out: [
-        "components/cronet/version.h",
-    ],
-    tool_files: [
-        "build/util/LASTCHANGE",
-        "build/util/android_chrome_version.py",
-        "build/util/version.py",
-        "chrome/VERSION",
-        "components/cronet/version.h.in",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/cronet:metrics_util
-cc_object {
-    name: "cronet_aml_components_cronet_metrics_util",
-    srcs: [
-        "components/cronet/metrics_util.cc",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //components/cronet/native:cronet_native_headers
-cc_object {
-    name: "cronet_aml_components_cronet_native_cronet_native_headers",
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "components/cronet/native/generated/",
-        "components/cronet/native/include/",
-        "components/grpc_support/include/",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //components/cronet/native:cronet_native_impl
-cc_object {
-    name: "cronet_aml_components_cronet_native_cronet_native_impl",
-    srcs: [
-        "components/cronet/native/buffer.cc",
-        "components/cronet/native/engine.cc",
-        "components/cronet/native/generated/cronet.idl_impl_interface.cc",
-        "components/cronet/native/generated/cronet.idl_impl_struct.cc",
-        "components/cronet/native/io_buffer_with_cronet_buffer.cc",
-        "components/cronet/native/native_metrics_util.cc",
-        "components/cronet/native/runnables.cc",
-        "components/cronet/native/upload_data_sink.cc",
-        "components/cronet/native/url_request.cc",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_components_prefs_prefs",
-        "cronet_aml_crypto_crypto",
-        "cronet_aml_net_net",
-        "cronet_aml_net_preload_decoder",
-        "cronet_aml_net_third_party_quiche_quiche",
-        "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_brotli_common",
-        "cronet_aml_third_party_brotli_dec",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-        "cronet_aml_third_party_protobuf_protobuf_lite",
-        "cronet_aml_third_party_zlib_zlib",
-        "cronet_aml_url_url",
-    ],
-    generated_headers: [
-        "cronet_aml_third_party_metrics_proto_metrics_proto_gen_headers",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "components/cronet/native/generated/",
-        "components/cronet/native/include/",
-        "components/grpc_support/include/",
-        "net/third_party/quiche/overrides/",
-        "net/third_party/quiche/src/",
-        "net/third_party/quiche/src/quiche/common/platform/default/",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-        "third_party/protobuf/src/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            generated_headers: [
-                "cronet_aml_components_cronet_cronet_buildflags__android_arm",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_arm",
-            ],
-        },
-        android_arm64: {
-            generated_headers: [
-                "cronet_aml_components_cronet_cronet_buildflags__android_arm64",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_components_cronet_cronet_buildflags__android_x86",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_components_cronet_cronet_buildflags__android_x86_64",
-                "cronet_aml_components_cronet_cronet_version_header_action__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //components/grpc_support:grpc_support
-cc_object {
-    name: "cronet_aml_components_grpc_support_grpc_support",
-    srcs: [
-        "components/grpc_support/bidirectional_stream.cc",
-        "components/grpc_support/bidirectional_stream_c.cc",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_crypto_crypto",
-        "cronet_aml_net_net",
-        "cronet_aml_net_preload_decoder",
-        "cronet_aml_net_third_party_quiche_quiche",
-        "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_brotli_common",
-        "cronet_aml_third_party_brotli_dec",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-        "cronet_aml_third_party_protobuf_protobuf_lite",
-        "cronet_aml_third_party_zlib_zlib",
-        "cronet_aml_url_url",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "net/third_party/quiche/overrides/",
-        "net/third_party/quiche/src/",
-        "net/third_party/quiche/src/quiche/common/platform/default/",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-        "third_party/protobuf/src/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //components/grpc_support:headers
-cc_object {
-    name: "cronet_aml_components_grpc_support_headers",
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //components/metrics:library_support
-cc_object {
-    name: "cronet_aml_components_metrics_library_support",
-    srcs: [
-        ":cronet_aml_third_party_metrics_proto_metrics_proto_gen",
-        "components/metrics/histogram_encoder.cc",
-        "components/metrics/library_support/histogram_manager.cc",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-        "libprotobuf-cpp-lite",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-        "cronet_aml_third_party_protobuf_protobuf_lite",
-    ],
-    generated_headers: [
-        "cronet_aml_third_party_metrics_proto_metrics_proto_gen_headers",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-        "third_party/protobuf/src/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //components/nacl/common:buildflags__host
-cc_genrule {
-    name: "cronet_aml_components_nacl_common_buildflags__host",
-    cmd: "echo '--flags ENABLE_NACL=\"true\" IS_MINIMAL_TOOLCHAIN=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//components/nacl/common:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "components/nacl/common/buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/prefs/android:jni_headers__android_arm
-cc_genrule {
-    name: "cronet_aml_components_prefs_android_jni_headers__android_arm",
-    srcs: [
-        "components/prefs/android/java/src/org/chromium/components/prefs/PrefService.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/components/prefs/android/jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "PrefService_jni.h " +
-         "--input_file " +
-         "$(location components/prefs/android/java/src/org/chromium/components/prefs/PrefService.java)",
-    out: [
-        "components/prefs/android/jni_headers/PrefService_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/prefs/android:jni_headers__android_arm64
-cc_genrule {
-    name: "cronet_aml_components_prefs_android_jni_headers__android_arm64",
-    srcs: [
-        "components/prefs/android/java/src/org/chromium/components/prefs/PrefService.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/components/prefs/android/jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "PrefService_jni.h " +
-         "--input_file " +
-         "$(location components/prefs/android/java/src/org/chromium/components/prefs/PrefService.java)",
-    out: [
-        "components/prefs/android/jni_headers/PrefService_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/prefs/android:jni_headers__android_x86
-cc_genrule {
-    name: "cronet_aml_components_prefs_android_jni_headers__android_x86",
-    srcs: [
-        "components/prefs/android/java/src/org/chromium/components/prefs/PrefService.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/components/prefs/android/jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "PrefService_jni.h " +
-         "--input_file " +
-         "$(location components/prefs/android/java/src/org/chromium/components/prefs/PrefService.java)",
-    out: [
-        "components/prefs/android/jni_headers/PrefService_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/prefs/android:jni_headers__android_x86_64
-cc_genrule {
-    name: "cronet_aml_components_prefs_android_jni_headers__android_x86_64",
-    srcs: [
-        "components/prefs/android/java/src/org/chromium/components/prefs/PrefService.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/components/prefs/android/jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "PrefService_jni.h " +
-         "--input_file " +
-         "$(location components/prefs/android/java/src/org/chromium/components/prefs/PrefService.java)",
-    out: [
-        "components/prefs/android/jni_headers/PrefService_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //components/prefs:prefs
-cc_library_static {
-    name: "cronet_aml_components_prefs_prefs",
-    srcs: [
-        "components/prefs/android/pref_service_android.cc",
-        "components/prefs/command_line_pref_store.cc",
-        "components/prefs/default_pref_store.cc",
-        "components/prefs/in_memory_pref_store.cc",
-        "components/prefs/json_pref_store.cc",
-        "components/prefs/overlay_user_pref_store.cc",
-        "components/prefs/persistent_pref_store.cc",
-        "components/prefs/pref_change_registrar.cc",
-        "components/prefs/pref_member.cc",
-        "components/prefs/pref_notifier_impl.cc",
-        "components/prefs/pref_registry.cc",
-        "components/prefs/pref_registry_simple.cc",
-        "components/prefs/pref_service.cc",
-        "components/prefs/pref_service_factory.cc",
-        "components/prefs/pref_store.cc",
-        "components/prefs/pref_value_map.cc",
-        "components/prefs/pref_value_store.cc",
-        "components/prefs/scoped_user_pref_update.cc",
-        "components/prefs/segregated_pref_store.cc",
-        "components/prefs/value_map_pref_store.cc",
-        "components/prefs/writeable_pref_store.cc",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCOMPONENTS_PREFS_IMPLEMENTATION",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_components_prefs_android_jni_headers__android_arm",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_components_prefs_android_jni_headers__android_arm",
-            ],
-        },
-        android_arm64: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_components_prefs_android_jni_headers__android_arm64",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_components_prefs_android_jni_headers__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_components_prefs_android_jni_headers__android_x86",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_components_prefs_android_jni_headers__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_components_prefs_android_jni_headers__android_x86_64",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_components_prefs_android_jni_headers__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //crypto:buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_crypto_buildflags__android_arm",
-    cmd: "echo '--flags USE_NSS_CERTS=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//crypto:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "crypto/crypto_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //crypto:buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_crypto_buildflags__android_arm64",
-    cmd: "echo '--flags USE_NSS_CERTS=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//crypto:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "crypto/crypto_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //crypto:buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_crypto_buildflags__android_x86",
-    cmd: "echo '--flags USE_NSS_CERTS=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//crypto:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "crypto/crypto_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //crypto:buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_crypto_buildflags__android_x86_64",
-    cmd: "echo '--flags USE_NSS_CERTS=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//crypto:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "crypto/crypto_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //crypto:buildflags__host
-cc_genrule {
-    name: "cronet_aml_crypto_buildflags__host",
-    cmd: "echo '--flags USE_NSS_CERTS=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//crypto:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    host_supported: true,
-    device_supported: false,
-    out: [
-        "crypto/crypto_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //crypto:crypto
-cc_library_static {
-    name: "cronet_aml_crypto_crypto",
-    srcs: [
-        "crypto/aead.cc",
-        "crypto/ec_private_key.cc",
-        "crypto/ec_signature_creator.cc",
-        "crypto/ec_signature_creator_impl.cc",
-        "crypto/encryptor.cc",
-        "crypto/hkdf.cc",
-        "crypto/hmac.cc",
-        "crypto/openssl_util.cc",
-        "crypto/p224_spake.cc",
-        "crypto/random.cc",
-        "crypto/rsa_private_key.cc",
-        "crypto/secure_hash.cc",
-        "crypto/secure_util.cc",
-        "crypto/sha2.cc",
-        "crypto/signature_creator.cc",
-        "crypto/signature_verifier.cc",
-        "crypto/symmetric_key.cc",
-        "crypto/unexportable_key.cc",
-        "crypto/unexportable_key_metrics.cc",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCRYPTO_IMPLEMENTATION",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android: {
-            shared_libs: [
-                "libandroid",
-                "liblog",
-            ],
-        },
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_crypto_buildflags__android_arm",
-            ],
-            export_generated_headers: [
-                "cronet_aml_crypto_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_crypto_buildflags__android_arm64",
-            ],
-            export_generated_headers: [
-                "cronet_aml_crypto_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_crypto_buildflags__android_x86",
-            ],
-            export_generated_headers: [
-                "cronet_aml_crypto_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_crypto_buildflags__android_x86_64",
-            ],
-            export_generated_headers: [
-                "cronet_aml_crypto_buildflags__android_x86_64",
-            ],
-        },
-        host: {
-            srcs: [
-                "crypto/nss_key_util.cc",
-                "crypto/nss_util.cc",
-            ],
-            static_libs: [
-                "cronet_aml_base_third_party_symbolize_symbolize",
-                "cronet_aml_base_third_party_xdg_mime_xdg_mime",
-                "cronet_aml_base_third_party_xdg_user_dirs_xdg_user_dirs",
-            ],
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-            local_include_dirs: [
-                "build/linux/debian_bullseye_amd64-sysroot/usr/include/nspr",
-                "build/linux/debian_bullseye_amd64-sysroot/usr/include/nss",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__host",
-                "cronet_aml_components_nacl_common_buildflags__host",
-                "cronet_aml_crypto_buildflags__host",
-            ],
-            export_generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__host",
-                "cronet_aml_components_nacl_common_buildflags__host",
-                "cronet_aml_crypto_buildflags__host",
-            ],
-        },
-    },
-}
-
-// GN: //gn:default_deps
-cc_defaults {
-    name: "cronet_aml_defaults",
-    cflags: [
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-O2",
-        "-Wno-ambiguous-reversed-operator",
-        "-Wno-deprecated-non-prototype",
-        "-Wno-error=return-type",
-        "-Wno-macro-redefined",
-        "-Wno-missing-field-initializers",
-        "-Wno-non-virtual-dtor",
-        "-Wno-null-pointer-subtraction",
-        "-Wno-sign-compare",
-        "-Wno-sign-promo",
-        "-Wno-unreachable-code-loop-increment",
-        "-Wno-unused-parameter",
-        "-fPIC",
-        "-fvisibility=hidden",
-    ],
-    stl: "none",
-    apex_available: [
-        "com.android.tethering",
-    ],
-    min_sdk_version: "29",
-    target: {
-        android: {
-            header_libs: [
-                "jni_headers",
-                "media_ndk_headers",
-            ],
-        },
-        host: {
-            cflags: [
-                "-UANDROID",
-            ],
-        },
-    },
-}
-
-// GN: //ipc:param_traits
-cc_object {
-    name: "cronet_aml_ipc_param_traits",
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //gn:java
-java_library {
-    name: "cronet_aml_java",
-    srcs: [
-        ":cronet_aml_base_base_android_java_enums_srcjar",
-        ":cronet_aml_base_java_features_srcjar",
-        ":cronet_aml_base_java_switches_srcjar",
-        ":cronet_aml_build_android_build_config_gen",
-        ":cronet_aml_build_android_native_libraries_gen",
-        ":cronet_aml_components_cronet_android_cronet_jni_registration__java",
-        ":cronet_aml_components_cronet_android_http_cache_type_java",
-        ":cronet_aml_components_cronet_android_implementation_api_version",
-        ":cronet_aml_components_cronet_android_integrated_mode_state",
-        ":cronet_aml_components_cronet_android_interface_api_version",
-        ":cronet_aml_components_cronet_android_load_states_list",
-        ":cronet_aml_components_cronet_android_net_idempotency_java",
-        ":cronet_aml_components_cronet_android_net_request_priority_java",
-        ":cronet_aml_components_cronet_android_network_quality_observation_source_java",
-        ":cronet_aml_components_cronet_android_rtt_throughput_values_java",
-        ":cronet_aml_components_cronet_android_url_request_error_java",
-        ":cronet_aml_net_android_net_android_java_enums_srcjar",
-        ":cronet_aml_net_android_net_errors_java",
-        ":cronet_aml_net_effective_connection_type_java",
-        "base/android/java/src/org/chromium/base/ActivityState.java",
-        "base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java",
-        "base/android/java/src/org/chromium/base/ApkAssets.java",
-        "base/android/java/src/org/chromium/base/ApplicationStatus.java",
-        "base/android/java/src/org/chromium/base/BaseFeatureList.java",
-        "base/android/java/src/org/chromium/base/BuildInfo.java",
-        "base/android/java/src/org/chromium/base/BundleUtils.java",
-        "base/android/java/src/org/chromium/base/ByteArrayGenerator.java",
-        "base/android/java/src/org/chromium/base/Callback.java",
-        "base/android/java/src/org/chromium/base/CallbackController.java",
-        "base/android/java/src/org/chromium/base/CollectionUtil.java",
-        "base/android/java/src/org/chromium/base/CommandLine.java",
-        "base/android/java/src/org/chromium/base/CommandLineInitUtil.java",
-        "base/android/java/src/org/chromium/base/Consumer.java",
-        "base/android/java/src/org/chromium/base/ContentUriUtils.java",
-        "base/android/java/src/org/chromium/base/ContextUtils.java",
-        "base/android/java/src/org/chromium/base/CpuFeatures.java",
-        "base/android/java/src/org/chromium/base/DiscardableReferencePool.java",
-        "base/android/java/src/org/chromium/base/EarlyTraceEvent.java",
-        "base/android/java/src/org/chromium/base/EventLog.java",
-        "base/android/java/src/org/chromium/base/FeatureList.java",
-        "base/android/java/src/org/chromium/base/Features.java",
-        "base/android/java/src/org/chromium/base/FieldTrialList.java",
-        "base/android/java/src/org/chromium/base/FileUtils.java",
-        "base/android/java/src/org/chromium/base/Function.java",
-        "base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java",
-        "base/android/java/src/org/chromium/base/IntStringCallback.java",
-        "base/android/java/src/org/chromium/base/IntentUtils.java",
-        "base/android/java/src/org/chromium/base/JNIUtils.java",
-        "base/android/java/src/org/chromium/base/JavaExceptionReporter.java",
-        "base/android/java/src/org/chromium/base/JavaHandlerThread.java",
-        "base/android/java/src/org/chromium/base/JniException.java",
-        "base/android/java/src/org/chromium/base/JniStaticTestMocker.java",
-        "base/android/java/src/org/chromium/base/LifetimeAssert.java",
-        "base/android/java/src/org/chromium/base/LocaleUtils.java",
-        "base/android/java/src/org/chromium/base/Log.java",
-        "base/android/java/src/org/chromium/base/MathUtils.java",
-        "base/android/java/src/org/chromium/base/MemoryPressureListener.java",
-        "base/android/java/src/org/chromium/base/NativeLibraryLoadedStatus.java",
-        "base/android/java/src/org/chromium/base/ObserverList.java",
-        "base/android/java/src/org/chromium/base/PackageManagerUtils.java",
-        "base/android/java/src/org/chromium/base/PackageUtils.java",
-        "base/android/java/src/org/chromium/base/PathService.java",
-        "base/android/java/src/org/chromium/base/PathUtils.java",
-        "base/android/java/src/org/chromium/base/PiiElider.java",
-        "base/android/java/src/org/chromium/base/PowerMonitor.java",
-        "base/android/java/src/org/chromium/base/PowerMonitorForQ.java",
-        "base/android/java/src/org/chromium/base/Predicate.java",
-        "base/android/java/src/org/chromium/base/Promise.java",
-        "base/android/java/src/org/chromium/base/RadioUtils.java",
-        "base/android/java/src/org/chromium/base/StreamUtil.java",
-        "base/android/java/src/org/chromium/base/StrictModeContext.java",
-        "base/android/java/src/org/chromium/base/SysUtils.java",
-        "base/android/java/src/org/chromium/base/ThreadUtils.java",
-        "base/android/java/src/org/chromium/base/TimeUtils.java",
-        "base/android/java/src/org/chromium/base/TimezoneUtils.java",
-        "base/android/java/src/org/chromium/base/TraceEvent.java",
-        "base/android/java/src/org/chromium/base/UnguessableToken.java",
-        "base/android/java/src/org/chromium/base/UnownedUserData.java",
-        "base/android/java/src/org/chromium/base/UnownedUserDataHost.java",
-        "base/android/java/src/org/chromium/base/UnownedUserDataKey.java",
-        "base/android/java/src/org/chromium/base/UserData.java",
-        "base/android/java/src/org/chromium/base/UserDataHost.java",
-        "base/android/java/src/org/chromium/base/WrappedClassLoader.java",
-        "base/android/java/src/org/chromium/base/annotations/AccessedByNative.java",
-        "base/android/java/src/org/chromium/base/annotations/CalledByNative.java",
-        "base/android/java/src/org/chromium/base/annotations/CalledByNativeForTesting.java",
-        "base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java",
-        "base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java",
-        "base/android/java/src/org/chromium/base/annotations/JNINamespace.java",
-        "base/android/java/src/org/chromium/base/annotations/JniIgnoreNatives.java",
-        "base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java",
-        "base/android/java/src/org/chromium/base/annotations/NativeMethods.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForM.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForN.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForO.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForOMR1.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForP.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForQ.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForR.java",
-        "base/android/java/src/org/chromium/base/compat/ApiHelperForS.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/DummyJankTracker.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/FrameMetrics.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsListener.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsStore.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankActivityTracker.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetricCalculator.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankMetrics.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankReportingRunnable.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankReportingScheduler.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankScenario.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankTracker.java",
-        "base/android/java/src/org/chromium/base/jank_tracker/JankTrackerImpl.java",
-        "base/android/java/src/org/chromium/base/library_loader/LegacyLinker.java",
-        "base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
-        "base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java",
-        "base/android/java/src/org/chromium/base/library_loader/Linker.java",
-        "base/android/java/src/org/chromium/base/library_loader/LinkerJni.java",
-        "base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java",
-        "base/android/java/src/org/chromium/base/library_loader/ModernLinker.java",
-        "base/android/java/src/org/chromium/base/library_loader/ModernLinkerJni.java",
-        "base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java",
-        "base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java",
-        "base/android/java/src/org/chromium/base/lifetime/DestroyChecker.java",
-        "base/android/java/src/org/chromium/base/lifetime/Destroyable.java",
-        "base/android/java/src/org/chromium/base/memory/JavaHeapDumpGenerator.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java",
-        "base/android/java/src/org/chromium/base/memory/MemoryPurgeManager.java",
-        "base/android/java/src/org/chromium/base/metrics/CachingUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/NativeUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/NoopUmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/RecordHistogram.java",
-        "base/android/java/src/org/chromium/base/metrics/RecordUserAction.java",
-        "base/android/java/src/org/chromium/base/metrics/ScopedSysTraceEvent.java",
-        "base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java",
-        "base/android/java/src/org/chromium/base/metrics/TimingMetric.java",
-        "base/android/java/src/org/chromium/base/metrics/UmaRecorder.java",
-        "base/android/java/src/org/chromium/base/metrics/UmaRecorderHolder.java",
-        "base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java",
-        "base/android/java/src/org/chromium/base/process_launcher/BindService.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnection.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionDelegate.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionFactory.java",
-        "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionImpl.java",
-        "base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java",
-        "base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl",
-        "base/android/java/src/org/chromium/base/process_launcher/IParentProcess.aidl",
-        "base/android/java/src/org/chromium/base/supplier/BooleanSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/DestroyableObservableSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/ObservableSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/ObservableSupplierImpl.java",
-        "base/android/java/src/org/chromium/base/supplier/OneShotCallback.java",
-        "base/android/java/src/org/chromium/base/supplier/OneshotSupplier.java",
-        "base/android/java/src/org/chromium/base/supplier/OneshotSupplierImpl.java",
-        "base/android/java/src/org/chromium/base/supplier/Supplier.java",
-        "base/android/java/src/org/chromium/base/supplier/UnownedUserDataSupplier.java",
-        "base/android/java/src/org/chromium/base/task/AsyncTask.java",
-        "base/android/java/src/org/chromium/base/task/BackgroundOnlyAsyncTask.java",
-        "base/android/java/src/org/chromium/base/task/ChainedTasks.java",
-        "base/android/java/src/org/chromium/base/task/ChoreographerTaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/ChromeThreadPoolExecutor.java",
-        "base/android/java/src/org/chromium/base/task/DefaultTaskExecutor.java",
-        "base/android/java/src/org/chromium/base/task/PostTask.java",
-        "base/android/java/src/org/chromium/base/task/SequencedTaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/SequencedTaskRunnerImpl.java",
-        "base/android/java/src/org/chromium/base/task/SerialExecutor.java",
-        "base/android/java/src/org/chromium/base/task/SingleThreadTaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/SingleThreadTaskRunnerImpl.java",
-        "base/android/java/src/org/chromium/base/task/TaskExecutor.java",
-        "base/android/java/src/org/chromium/base/task/TaskRunner.java",
-        "base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java",
-        "base/android/java/src/org/chromium/base/task/TaskTraits.java",
-        "base/android/java/src/org/chromium/base/task/TaskTraitsExtensionDescriptor.java",
-        "build/android/java/src/org/chromium/build/annotations/AlwaysInline.java",
-        "build/android/java/src/org/chromium/build/annotations/CheckDiscard.java",
-        "build/android/java/src/org/chromium/build/annotations/DoNotClassMerge.java",
-        "build/android/java/src/org/chromium/build/annotations/DoNotInline.java",
-        "build/android/java/src/org/chromium/build/annotations/IdentifierNameString.java",
-        "build/android/java/src/org/chromium/build/annotations/MainDex.java",
-        "build/android/java/src/org/chromium/build/annotations/MockedInTests.java",
-        "build/android/java/src/org/chromium/build/annotations/UsedByReflection.java",
-        "components/cronet/android/api/src/org/chromium/net/BidirectionalStream.java",
-        "components/cronet/android/api/src/org/chromium/net/CallbackException.java",
-        "components/cronet/android/api/src/org/chromium/net/CronetEngine.java",
-        "components/cronet/android/api/src/org/chromium/net/CronetException.java",
-        "components/cronet/android/api/src/org/chromium/net/CronetProvider.java",
-        "components/cronet/android/api/src/org/chromium/net/ExperimentalBidirectionalStream.java",
-        "components/cronet/android/api/src/org/chromium/net/ExperimentalCronetEngine.java",
-        "components/cronet/android/api/src/org/chromium/net/ExperimentalUrlRequest.java",
-        "components/cronet/android/api/src/org/chromium/net/ICronetEngineBuilder.java",
-        "components/cronet/android/api/src/org/chromium/net/InlineExecutionProhibitedException.java",
-        "components/cronet/android/api/src/org/chromium/net/NetworkException.java",
-        "components/cronet/android/api/src/org/chromium/net/NetworkQualityRttListener.java",
-        "components/cronet/android/api/src/org/chromium/net/NetworkQualityThroughputListener.java",
-        "components/cronet/android/api/src/org/chromium/net/QuicException.java",
-        "components/cronet/android/api/src/org/chromium/net/RequestFinishedInfo.java",
-        "components/cronet/android/api/src/org/chromium/net/UploadDataProvider.java",
-        "components/cronet/android/api/src/org/chromium/net/UploadDataProviders.java",
-        "components/cronet/android/api/src/org/chromium/net/UploadDataSink.java",
-        "components/cronet/android/api/src/org/chromium/net/UrlRequest.java",
-        "components/cronet/android/api/src/org/chromium/net/UrlResponseInfo.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/ByteArrayCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/ContentTypeParametersParser.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/CronetRequestCompletionListener.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/CronetResponse.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/ImplicitFlowControlCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/InMemoryTransformCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/JsonCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/RedirectHandler.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/RedirectHandlers.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/StringCronetCallback.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/UploadDataProviders.java",
-        "components/cronet/android/api/src/org/chromium/net/apihelpers/UrlRequestCallbacks.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetController.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetEngine.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetProvider.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeUrlRequest.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/FakeUrlResponse.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/ResponseMatcher.java",
-        "components/cronet/android/fake/java/org/chromium/net/test/UrlResponseMatcher.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/BidirectionalStreamBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/BidirectionalStreamNetworkException.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CallbackExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBase.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLibraryLoader.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLogger.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetLoggerFactory.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetManifest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetMetrics.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/InputStreamChannel.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngine.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngineBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetProvider.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaUploadDataSinkBase.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequest.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequestUtils.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderWithLibraryLoaderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetProvider.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/NoOpLogger.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/Preconditions.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/QuicExceptionImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/RequestFinishedInfoImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UrlRequestBase.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UrlRequestBuilderImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UrlResponseInfoImpl.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/UserAgent.java",
-        "components/cronet/android/java/src/org/chromium/net/impl/VersionSafeCallbacks.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetBufferedOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetChunkedOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetFixedModeOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLConnection.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLStreamHandler.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetInputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetOutputStream.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetURLStreamHandlerFactory.java",
-        "components/cronet/android/java/src/org/chromium/net/urlconnection/MessageLoop.java",
-        "net/android/java/src/org/chromium/net/AndroidCertVerifyResult.java",
-        "net/android/java/src/org/chromium/net/AndroidKeyStore.java",
-        "net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java",
-        "net/android/java/src/org/chromium/net/AndroidTrafficStats.java",
-        "net/android/java/src/org/chromium/net/ChromiumNetworkAdapter.java",
-        "net/android/java/src/org/chromium/net/DnsStatus.java",
-        "net/android/java/src/org/chromium/net/GURLUtils.java",
-        "net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java",
-        "net/android/java/src/org/chromium/net/HttpNegotiateConstants.java",
-        "net/android/java/src/org/chromium/net/HttpUtil.java",
-        "net/android/java/src/org/chromium/net/MimeTypeFilter.java",
-        "net/android/java/src/org/chromium/net/NetStringUtil.java",
-        "net/android/java/src/org/chromium/net/NetworkActiveNotifier.java",
-        "net/android/java/src/org/chromium/net/NetworkChangeNotifier.java",
-        "net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java",
-        "net/android/java/src/org/chromium/net/NetworkTrafficAnnotationTag.java",
-        "net/android/java/src/org/chromium/net/ProxyBroadcastReceiver.java",
-        "net/android/java/src/org/chromium/net/ProxyChangeListener.java",
-        "net/android/java/src/org/chromium/net/RegistrationPolicyAlwaysRegister.java",
-        "net/android/java/src/org/chromium/net/RegistrationPolicyApplicationStatus.java",
-        "net/android/java/src/org/chromium/net/ThreadStatsUid.java",
-        "net/android/java/src/org/chromium/net/X509Util.java",
-        "url/android/java/src/org/chromium/url/IDNStringUtil.java",
-    ],
-    apex_available: [
-        "//apex_available:platform",
-        "com.android.tethering",
-    ],
-    libs: [
-        "android-support-multidex",
-        "androidx.annotation_annotation",
-        "androidx.annotation_annotation-experimental-nodeps",
-        "androidx.collection_collection",
-        "androidx.core_core-nodeps",
-        "framework-connectivity-t.stubs.module_lib",
-        "framework-connectivity.stubs.module_lib",
-        "framework-mediaprovider.stubs.module_lib",
-        "framework-tethering.stubs.module_lib",
-        "framework-wifi.stubs.module_lib",
-        "jsr305",
-    ],
-    aidl: {
-        include_dirs: [
-            "frameworks/base/core/java/",
-        ],
-        local_include_dirs: [
-            "base/android/java/src/",
-        ],
-    },
-    plugins: [
-        "cronet_aml_java_jni_annotation_preprocessor",
-    ],
-    sdk_version: "module_current",
-}
-
-// GN: //base/android/jni_generator:jni_processor
-java_plugin {
-    name: "cronet_aml_java_jni_annotation_preprocessor",
-    srcs: [
-        ":cronet_aml_build_android_build_config_gen",
-        "base/android/java/src/org/chromium/base/JniException.java",
-        "base/android/java/src/org/chromium/base/JniStaticTestMocker.java",
-        "base/android/java/src/org/chromium/base/NativeLibraryLoadedStatus.java",
-        "base/android/java/src/org/chromium/base/annotations/NativeMethods.java",
-        "base/android/jni_generator/java/src/org/chromium/jni_generator/JniProcessor.java",
-        "build/android/java/src/org/chromium/build/annotations/CheckDiscard.java",
-        "build/android/java/src/org/chromium/build/annotations/MainDex.java",
-    ],
-    static_libs: [
-        "auto_service_annotations",
-        "guava",
-        "javapoet",
-    ],
-    processor_class: "org.chromium.jni_generator.JniProcessor",
-}
-
-// GN: //net/android:net_android_java_enums_srcjar
-java_genrule {
-    name: "cronet_aml_net_android_net_android_java_enums_srcjar",
-    srcs: [
-        "net/android/network_change_notifier_android.cc",
-        "net/android/traffic_stats.cc",
-        "net/socket/socket_tag.cc",
-    ],
-    cmd: "$(location build/android/gyp/java_cpp_enum.py) --srcjar " +
-         "$(out) " +
-         "$(location net/base/network_change_notifier.h) " +
-         "$(location net/socket/socket_tag.cc) " +
-         "$(location net/android/cert_verify_result_android.h) " +
-         "$(location net/android/keystore.h) " +
-         "$(location net/android/network_change_notifier_android.cc) " +
-         "$(location net/android/traffic_stats.cc)",
-    out: [
-        "net/android/net_android_java_enums_srcjar.srcjar",
-    ],
-    tool_files: [
-        "build/android/gyp/java_cpp_enum.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/android/gyp/util/java_cpp_utils.py",
-        "build/gn_helpers.py",
-        "net/android/cert_verify_result_android.h",
-        "net/android/keystore.h",
-        "net/base/network_change_notifier.h",
-    ],
-}
-
-// GN: //net/android:net_errors_java
-genrule {
-    name: "cronet_aml_net_android_net_errors_java",
-    srcs: [
-        ":cronet_aml_net_android_net_errors_java_preprocess",
-    ],
-    tools: [
-        "soong_zip",
-    ],
-    cmd: "cp $(in) $(genDir)/NetError.java && " +
-         "$(location soong_zip) -o $(out) -srcjar -f $(genDir)/NetError.java",
-    out: [
-        "NetError.srcjar",
-    ],
-}
-
-// GN: //net/android:net_errors_java
-cc_object {
-    name: "cronet_aml_net_android_net_errors_java_preprocess",
-    srcs: [
-        ":cronet_aml_net_android_net_errors_java_rename",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-E",
-        "-P",
-    ],
-    compile_multilib: "first",
-}
-
-// GN: //net/android:net_errors_java
-genrule {
-    name: "cronet_aml_net_android_net_errors_java_rename",
-    srcs: [
-        "net/android/java/NetError.template",
-    ],
-    cmd: "cp $(in) $(out)",
-    out: [
-        "NetError.cc",
-    ],
-}
-
-// GN: //net/base/registry_controlled_domains:registry_controlled_domains__android_arm
-cc_genrule {
-    name: "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm",
-    cmd: "$(location net/tools/dafsa/make_dafsa.py) --reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest1.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest1-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest2.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest2-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest3.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest3-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest4.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest4-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest5.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest5-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest6.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest6-reversed-inc.cc)",
-    out: [
-        "net/base/registry_controlled_domains/effective_tld_names-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest1-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest2-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest3-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest4-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest5-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest6-reversed-inc.cc",
-    ],
-    tool_files: [
-        "net/base/registry_controlled_domains/effective_tld_names.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest1.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest2.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest3.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest4.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest5.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest6.gperf",
-        "net/tools/dafsa/make_dafsa.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net/base/registry_controlled_domains:registry_controlled_domains__android_arm64
-cc_genrule {
-    name: "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm64",
-    cmd: "$(location net/tools/dafsa/make_dafsa.py) --reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest1.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest1-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest2.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest2-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest3.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest3-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest4.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest4-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest5.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest5-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest6.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest6-reversed-inc.cc)",
-    out: [
-        "net/base/registry_controlled_domains/effective_tld_names-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest1-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest2-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest3-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest4-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest5-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest6-reversed-inc.cc",
-    ],
-    tool_files: [
-        "net/base/registry_controlled_domains/effective_tld_names.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest1.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest2.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest3.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest4.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest5.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest6.gperf",
-        "net/tools/dafsa/make_dafsa.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net/base/registry_controlled_domains:registry_controlled_domains__android_x86
-cc_genrule {
-    name: "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86",
-    cmd: "$(location net/tools/dafsa/make_dafsa.py) --reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest1.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest1-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest2.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest2-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest3.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest3-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest4.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest4-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest5.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest5-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest6.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest6-reversed-inc.cc)",
-    out: [
-        "net/base/registry_controlled_domains/effective_tld_names-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest1-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest2-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest3-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest4-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest5-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest6-reversed-inc.cc",
-    ],
-    tool_files: [
-        "net/base/registry_controlled_domains/effective_tld_names.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest1.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest2.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest3.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest4.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest5.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest6.gperf",
-        "net/tools/dafsa/make_dafsa.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net/base/registry_controlled_domains:registry_controlled_domains__android_x86_64
-cc_genrule {
-    name: "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86_64",
-    cmd: "$(location net/tools/dafsa/make_dafsa.py) --reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest1.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest1-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest2.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest2-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest3.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest3-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest4.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest4-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest5.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest5-reversed-inc.cc) " +
-         "&& python3 $(location net/tools/dafsa/make_dafsa.py) " +
-         "--reverse " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest6.gperf) " +
-         "$(location net/base/registry_controlled_domains/effective_tld_names_unittest6-reversed-inc.cc)",
-    out: [
-        "net/base/registry_controlled_domains/effective_tld_names-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest1-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest2-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest3-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest4-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest5-reversed-inc.cc",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest6-reversed-inc.cc",
-    ],
-    tool_files: [
-        "net/base/registry_controlled_domains/effective_tld_names.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest1.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest2.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest3.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest4.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest5.gperf",
-        "net/base/registry_controlled_domains/effective_tld_names_unittest6.gperf",
-        "net/tools/dafsa/make_dafsa.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net:buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_net_buildflags__android_arm",
-    cmd: "echo '--flags POSIX_BYPASS_MMAP=\"true\" DISABLE_FILE_SUPPORT=\"true\" ENABLE_MDNS=\"false\" ENABLE_REPORTING=\"true\" ENABLE_WEBSOCKETS=\"false\" INCLUDE_TRANSPORT_SECURITY_STATE_PRELOAD_LIST=\"false\" USE_KERBEROS=\"true\" USE_EXTERNAL_GSSAPI=\"false\" TRIAL_COMPARISON_CERT_VERIFIER_SUPPORTED=\"false\" CHROME_ROOT_STORE_SUPPORTED=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//net:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "net/net_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net:buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_net_buildflags__android_arm64",
-    cmd: "echo '--flags POSIX_BYPASS_MMAP=\"true\" DISABLE_FILE_SUPPORT=\"true\" ENABLE_MDNS=\"false\" ENABLE_REPORTING=\"true\" ENABLE_WEBSOCKETS=\"false\" INCLUDE_TRANSPORT_SECURITY_STATE_PRELOAD_LIST=\"false\" USE_KERBEROS=\"true\" USE_EXTERNAL_GSSAPI=\"false\" TRIAL_COMPARISON_CERT_VERIFIER_SUPPORTED=\"false\" CHROME_ROOT_STORE_SUPPORTED=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//net:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "net/net_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net:buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_net_buildflags__android_x86",
-    cmd: "echo '--flags POSIX_BYPASS_MMAP=\"false\" DISABLE_FILE_SUPPORT=\"true\" ENABLE_MDNS=\"false\" ENABLE_REPORTING=\"true\" ENABLE_WEBSOCKETS=\"false\" INCLUDE_TRANSPORT_SECURITY_STATE_PRELOAD_LIST=\"false\" USE_KERBEROS=\"true\" USE_EXTERNAL_GSSAPI=\"false\" TRIAL_COMPARISON_CERT_VERIFIER_SUPPORTED=\"false\" CHROME_ROOT_STORE_SUPPORTED=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//net:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "net/net_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net:buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_net_buildflags__android_x86_64",
-    cmd: "echo '--flags POSIX_BYPASS_MMAP=\"true\" DISABLE_FILE_SUPPORT=\"true\" ENABLE_MDNS=\"false\" ENABLE_REPORTING=\"true\" ENABLE_WEBSOCKETS=\"false\" INCLUDE_TRANSPORT_SECURITY_STATE_PRELOAD_LIST=\"false\" USE_KERBEROS=\"true\" USE_EXTERNAL_GSSAPI=\"false\" TRIAL_COMPARISON_CERT_VERIFIER_SUPPORTED=\"false\" CHROME_ROOT_STORE_SUPPORTED=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//net:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "net/net_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net:constants
-cc_object {
-    name: "cronet_aml_net_constants",
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //net/data/ssl/chrome_root_store:gen_root_store_inc__android_arm
-cc_genrule {
-    name: "cronet_aml_net_data_ssl_chrome_root_store_gen_root_store_inc__android_arm",
-    cmd: "$(location build/gn_run_binary.py) clang_x64/root_store_tool " +
-         "--root-store " +
-         "../../net/data/ssl/chrome_root_store/root_store.textproto " +
-         "--certs " +
-         "../../net/data/ssl/chrome_root_store/root_store.certs " +
-         "--write-cpp-root-store " +
-         "gen/net/data/ssl/chrome_root_store/chrome-root-store-inc.cc " +
-         "--write-cpp-ev-roots " +
-         "gen/net/data/ssl/chrome_root_store/chrome-ev-roots-inc.cc",
-    out: [
-        "net/data/ssl/chrome_root_store/chrome-ev-roots-inc.cc",
-        "net/data/ssl/chrome_root_store/chrome-root-store-inc.cc",
-    ],
-    tool_files: [
-        "build/gn_run_binary.py",
-        "net/data/ssl/chrome_root_store/root_store.certs",
-        "net/data/ssl/chrome_root_store/root_store.textproto",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net/data/ssl/chrome_root_store:gen_root_store_inc__android_arm64
-cc_genrule {
-    name: "cronet_aml_net_data_ssl_chrome_root_store_gen_root_store_inc__android_arm64",
-    cmd: "$(location build/gn_run_binary.py) clang_x64/root_store_tool " +
-         "--root-store " +
-         "../../net/data/ssl/chrome_root_store/root_store.textproto " +
-         "--certs " +
-         "../../net/data/ssl/chrome_root_store/root_store.certs " +
-         "--write-cpp-root-store " +
-         "gen/net/data/ssl/chrome_root_store/chrome-root-store-inc.cc " +
-         "--write-cpp-ev-roots " +
-         "gen/net/data/ssl/chrome_root_store/chrome-ev-roots-inc.cc",
-    out: [
-        "net/data/ssl/chrome_root_store/chrome-ev-roots-inc.cc",
-        "net/data/ssl/chrome_root_store/chrome-root-store-inc.cc",
-    ],
-    tool_files: [
-        "build/gn_run_binary.py",
-        "net/data/ssl/chrome_root_store/root_store.certs",
-        "net/data/ssl/chrome_root_store/root_store.textproto",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net/data/ssl/chrome_root_store:gen_root_store_inc__android_x86
-cc_genrule {
-    name: "cronet_aml_net_data_ssl_chrome_root_store_gen_root_store_inc__android_x86",
-    cmd: "$(location build/gn_run_binary.py) clang_x64/root_store_tool " +
-         "--root-store " +
-         "../../net/data/ssl/chrome_root_store/root_store.textproto " +
-         "--certs " +
-         "../../net/data/ssl/chrome_root_store/root_store.certs " +
-         "--write-cpp-root-store " +
-         "gen/net/data/ssl/chrome_root_store/chrome-root-store-inc.cc " +
-         "--write-cpp-ev-roots " +
-         "gen/net/data/ssl/chrome_root_store/chrome-ev-roots-inc.cc",
-    out: [
-        "net/data/ssl/chrome_root_store/chrome-ev-roots-inc.cc",
-        "net/data/ssl/chrome_root_store/chrome-root-store-inc.cc",
-    ],
-    tool_files: [
-        "build/gn_run_binary.py",
-        "net/data/ssl/chrome_root_store/root_store.certs",
-        "net/data/ssl/chrome_root_store/root_store.textproto",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net/data/ssl/chrome_root_store:gen_root_store_inc__android_x86_64
-cc_genrule {
-    name: "cronet_aml_net_data_ssl_chrome_root_store_gen_root_store_inc__android_x86_64",
-    cmd: "$(location build/gn_run_binary.py) clang_x64/root_store_tool " +
-         "--root-store " +
-         "../../net/data/ssl/chrome_root_store/root_store.textproto " +
-         "--certs " +
-         "../../net/data/ssl/chrome_root_store/root_store.certs " +
-         "--write-cpp-root-store " +
-         "gen/net/data/ssl/chrome_root_store/chrome-root-store-inc.cc " +
-         "--write-cpp-ev-roots " +
-         "gen/net/data/ssl/chrome_root_store/chrome-ev-roots-inc.cc",
-    out: [
-        "net/data/ssl/chrome_root_store/chrome-ev-roots-inc.cc",
-        "net/data/ssl/chrome_root_store/chrome-root-store-inc.cc",
-    ],
-    tool_files: [
-        "build/gn_run_binary.py",
-        "net/data/ssl/chrome_root_store/root_store.certs",
-        "net/data/ssl/chrome_root_store/root_store.textproto",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net/dns:dns
-cc_object {
-    name: "cronet_aml_net_dns_dns",
-    srcs: [
-        "net/dns/address_info.cc",
-        "net/dns/address_sorter_posix.cc",
-        "net/dns/context_host_resolver.cc",
-        "net/dns/dns_alias_utility.cc",
-        "net/dns/dns_client.cc",
-        "net/dns/dns_config.cc",
-        "net/dns/dns_config_service.cc",
-        "net/dns/dns_config_service_android.cc",
-        "net/dns/dns_hosts.cc",
-        "net/dns/dns_query.cc",
-        "net/dns/dns_reloader.cc",
-        "net/dns/dns_response.cc",
-        "net/dns/dns_response_result_extractor.cc",
-        "net/dns/dns_server_iterator.cc",
-        "net/dns/dns_session.cc",
-        "net/dns/dns_transaction.cc",
-        "net/dns/dns_udp_tracker.cc",
-        "net/dns/dns_util.cc",
-        "net/dns/host_cache.cc",
-        "net/dns/host_resolver.cc",
-        "net/dns/host_resolver_manager.cc",
-        "net/dns/host_resolver_mdns_listener_impl.cc",
-        "net/dns/host_resolver_mdns_task.cc",
-        "net/dns/host_resolver_nat64_task.cc",
-        "net/dns/host_resolver_proc.cc",
-        "net/dns/host_resolver_system_task.cc",
-        "net/dns/https_record_rdata.cc",
-        "net/dns/httpssvc_metrics.cc",
-        "net/dns/mapped_host_resolver.cc",
-        "net/dns/nsswitch_reader.cc",
-        "net/dns/opt_record_rdata.cc",
-        "net/dns/record_parsed.cc",
-        "net/dns/record_rdata.cc",
-        "net/dns/resolve_context.cc",
-        "net/dns/serial_worker.cc",
-        "net/dns/system_dns_config_change_notifier.cc",
-        "net/dns/test_dns_config_service.cc",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_crypto_crypto",
-        "cronet_aml_net_preload_decoder",
-        "cronet_aml_net_third_party_quiche_quiche",
-        "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_brotli_common",
-        "cronet_aml_third_party_brotli_dec",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-        "cronet_aml_third_party_protobuf_protobuf_lite",
-        "cronet_aml_third_party_zlib_zlib",
-        "cronet_aml_url_url",
-    ],
-    generated_headers: [
-        "cronet_aml_net_isolation_info_proto_gen_headers",
-        "cronet_aml_net_net_nqe_proto_gen_headers",
-        "cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen_headers",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DENABLE_BUILT_IN_DNS",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_SYS_UIO_H",
-        "-DNET_IMPLEMENTATION",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "net/third_party/quiche/overrides/",
-        "net/third_party/quiche/src/",
-        "net/third_party/quiche/src/quiche/common/platform/default/",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-        "third_party/brotli/include/",
-        "third_party/protobuf/src/",
-        "third_party/zlib/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm",
-                "cronet_aml_net_buildflags__android_arm",
-                "cronet_aml_net_net_jni_headers__android_arm",
-                "cronet_aml_url_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm64",
-                "cronet_aml_net_buildflags__android_arm64",
-                "cronet_aml_net_net_jni_headers__android_arm64",
-                "cronet_aml_url_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86",
-                "cronet_aml_net_buildflags__android_x86",
-                "cronet_aml_net_net_jni_headers__android_x86",
-                "cronet_aml_url_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86_64",
-                "cronet_aml_net_buildflags__android_x86_64",
-                "cronet_aml_net_net_jni_headers__android_x86_64",
-                "cronet_aml_url_buildflags__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //net/dns:dns_client
-cc_object {
-    name: "cronet_aml_net_dns_dns_client",
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_crypto_crypto",
-        "cronet_aml_net_preload_decoder",
-        "cronet_aml_net_third_party_quiche_quiche",
-        "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_brotli_common",
-        "cronet_aml_third_party_brotli_dec",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-        "cronet_aml_third_party_protobuf_protobuf_lite",
-        "cronet_aml_third_party_zlib_zlib",
-        "cronet_aml_url_url",
-    ],
-    generated_headers: [
-        "cronet_aml_net_isolation_info_proto_gen_headers",
-        "cronet_aml_net_net_nqe_proto_gen_headers",
-        "cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen_headers",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DENABLE_BUILT_IN_DNS",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_SYS_UIO_H",
-        "-DNET_IMPLEMENTATION",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "net/third_party/quiche/overrides/",
-        "net/third_party/quiche/src/",
-        "net/third_party/quiche/src/quiche/common/platform/default/",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-        "third_party/brotli/include/",
-        "third_party/protobuf/src/",
-        "third_party/zlib/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm",
-                "cronet_aml_net_buildflags__android_arm",
-                "cronet_aml_net_net_jni_headers__android_arm",
-                "cronet_aml_url_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm64",
-                "cronet_aml_net_buildflags__android_arm64",
-                "cronet_aml_net_net_jni_headers__android_arm64",
-                "cronet_aml_url_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86",
-                "cronet_aml_net_buildflags__android_x86",
-                "cronet_aml_net_net_jni_headers__android_x86",
-                "cronet_aml_url_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86_64",
-                "cronet_aml_net_buildflags__android_x86_64",
-                "cronet_aml_net_net_jni_headers__android_x86_64",
-                "cronet_aml_url_buildflags__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //net/dns:host_resolver
-cc_object {
-    name: "cronet_aml_net_dns_host_resolver",
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_crypto_crypto",
-        "cronet_aml_net_preload_decoder",
-        "cronet_aml_net_third_party_quiche_quiche",
-        "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_brotli_common",
-        "cronet_aml_third_party_brotli_dec",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-        "cronet_aml_third_party_protobuf_protobuf_lite",
-        "cronet_aml_third_party_zlib_zlib",
-        "cronet_aml_url_url",
-    ],
-    generated_headers: [
-        "cronet_aml_net_isolation_info_proto_gen_headers",
-        "cronet_aml_net_net_nqe_proto_gen_headers",
-        "cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen_headers",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DENABLE_BUILT_IN_DNS",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_SYS_UIO_H",
-        "-DNET_IMPLEMENTATION",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "net/third_party/quiche/overrides/",
-        "net/third_party/quiche/src/",
-        "net/third_party/quiche/src/quiche/common/platform/default/",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-        "third_party/brotli/include/",
-        "third_party/protobuf/src/",
-        "third_party/zlib/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm",
-                "cronet_aml_net_buildflags__android_arm",
-                "cronet_aml_net_net_jni_headers__android_arm",
-                "cronet_aml_url_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm64",
-                "cronet_aml_net_buildflags__android_arm64",
-                "cronet_aml_net_net_jni_headers__android_arm64",
-                "cronet_aml_url_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86",
-                "cronet_aml_net_buildflags__android_x86",
-                "cronet_aml_net_net_jni_headers__android_x86",
-                "cronet_aml_url_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86_64",
-                "cronet_aml_net_buildflags__android_x86_64",
-                "cronet_aml_net_net_jni_headers__android_x86_64",
-                "cronet_aml_url_buildflags__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //net/dns:host_resolver_manager
-cc_object {
-    name: "cronet_aml_net_dns_host_resolver_manager",
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_crypto_crypto",
-        "cronet_aml_net_preload_decoder",
-        "cronet_aml_net_third_party_quiche_quiche",
-        "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_brotli_common",
-        "cronet_aml_third_party_brotli_dec",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-        "cronet_aml_third_party_protobuf_protobuf_lite",
-        "cronet_aml_third_party_zlib_zlib",
-        "cronet_aml_url_url",
-    ],
-    generated_headers: [
-        "cronet_aml_net_isolation_info_proto_gen_headers",
-        "cronet_aml_net_net_nqe_proto_gen_headers",
-        "cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen_headers",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DENABLE_BUILT_IN_DNS",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_SYS_UIO_H",
-        "-DNET_IMPLEMENTATION",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "net/third_party/quiche/overrides/",
-        "net/third_party/quiche/src/",
-        "net/third_party/quiche/src/quiche/common/platform/default/",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-        "third_party/brotli/include/",
-        "third_party/protobuf/src/",
-        "third_party/zlib/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm",
-                "cronet_aml_net_buildflags__android_arm",
-                "cronet_aml_net_net_jni_headers__android_arm",
-                "cronet_aml_url_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm64",
-                "cronet_aml_net_buildflags__android_arm64",
-                "cronet_aml_net_net_jni_headers__android_arm64",
-                "cronet_aml_url_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86",
-                "cronet_aml_net_buildflags__android_x86",
-                "cronet_aml_net_net_jni_headers__android_x86",
-                "cronet_aml_url_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86_64",
-                "cronet_aml_net_buildflags__android_x86_64",
-                "cronet_aml_net_net_jni_headers__android_x86_64",
-                "cronet_aml_url_buildflags__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //net/dns:mdns_client
-cc_object {
-    name: "cronet_aml_net_dns_mdns_client",
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_crypto_crypto",
-        "cronet_aml_net_preload_decoder",
-        "cronet_aml_net_third_party_quiche_quiche",
-        "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_brotli_common",
-        "cronet_aml_third_party_brotli_dec",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-        "cronet_aml_third_party_protobuf_protobuf_lite",
-        "cronet_aml_third_party_zlib_zlib",
-        "cronet_aml_url_url",
-    ],
-    generated_headers: [
-        "cronet_aml_net_isolation_info_proto_gen_headers",
-        "cronet_aml_net_net_nqe_proto_gen_headers",
-        "cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen_headers",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DENABLE_BUILT_IN_DNS",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_SYS_UIO_H",
-        "-DNET_IMPLEMENTATION",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "net/third_party/quiche/overrides/",
-        "net/third_party/quiche/src/",
-        "net/third_party/quiche/src/quiche/common/platform/default/",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-        "third_party/brotli/include/",
-        "third_party/protobuf/src/",
-        "third_party/zlib/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm",
-                "cronet_aml_net_buildflags__android_arm",
-                "cronet_aml_net_net_jni_headers__android_arm",
-                "cronet_aml_url_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm64",
-                "cronet_aml_net_buildflags__android_arm64",
-                "cronet_aml_net_net_jni_headers__android_arm64",
-                "cronet_aml_url_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86",
-                "cronet_aml_net_buildflags__android_x86",
-                "cronet_aml_net_net_jni_headers__android_x86",
-                "cronet_aml_url_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86_64",
-                "cronet_aml_net_buildflags__android_x86_64",
-                "cronet_aml_net_net_jni_headers__android_x86_64",
-                "cronet_aml_url_buildflags__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //net/dns/public:public
-cc_object {
-    name: "cronet_aml_net_dns_public_public",
-    srcs: [
-        "net/dns/public/dns_config_overrides.cc",
-        "net/dns/public/dns_over_https_config.cc",
-        "net/dns/public/dns_over_https_server_config.cc",
-        "net/dns/public/dns_query_type.cc",
-        "net/dns/public/doh_provider_entry.cc",
-        "net/dns/public/host_resolver_results.cc",
-        "net/dns/public/resolve_error_info.cc",
-        "net/dns/public/util.cc",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_crypto_crypto",
-        "cronet_aml_net_preload_decoder",
-        "cronet_aml_net_third_party_quiche_quiche",
-        "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_brotli_common",
-        "cronet_aml_third_party_brotli_dec",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-        "cronet_aml_third_party_protobuf_protobuf_lite",
-        "cronet_aml_third_party_zlib_zlib",
-        "cronet_aml_url_url",
-    ],
-    generated_headers: [
-        "cronet_aml_net_isolation_info_proto_gen_headers",
-        "cronet_aml_net_net_nqe_proto_gen_headers",
-        "cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen_headers",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DENABLE_BUILT_IN_DNS",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_SYS_UIO_H",
-        "-DNET_IMPLEMENTATION",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "net/third_party/quiche/overrides/",
-        "net/third_party/quiche/src/",
-        "net/third_party/quiche/src/quiche/common/platform/default/",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-        "third_party/brotli/include/",
-        "third_party/protobuf/src/",
-        "third_party/zlib/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm",
-                "cronet_aml_net_buildflags__android_arm",
-                "cronet_aml_net_net_jni_headers__android_arm",
-                "cronet_aml_url_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm64",
-                "cronet_aml_net_buildflags__android_arm64",
-                "cronet_aml_net_net_jni_headers__android_arm64",
-                "cronet_aml_url_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86",
-                "cronet_aml_net_buildflags__android_x86",
-                "cronet_aml_net_net_jni_headers__android_x86",
-                "cronet_aml_url_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86_64",
-                "cronet_aml_net_buildflags__android_x86_64",
-                "cronet_aml_net_net_jni_headers__android_x86_64",
-                "cronet_aml_url_buildflags__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //net:effective_connection_type_java
-java_genrule {
-    name: "cronet_aml_net_effective_connection_type_java",
-    cmd: "$(location build/android/gyp/java_cpp_enum.py) --srcjar " +
-         "$(out) " +
-         "$(location net/nqe/effective_connection_type.h)",
-    out: [
-        "net/effective_connection_type_java.srcjar",
-    ],
-    tool_files: [
-        "build/android/gyp/java_cpp_enum.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/android/gyp/util/java_cpp_utils.py",
-        "build/gn_helpers.py",
-        "net/nqe/effective_connection_type.h",
-    ],
-}
-
-// GN: //net/http:transport_security_state_generated_files
-cc_object {
-    name: "cronet_aml_net_http_transport_security_state_generated_files",
-    srcs: [
-        "net/http/transport_security_state.cc",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_crypto_crypto",
-        "cronet_aml_net_preload_decoder",
-        "cronet_aml_net_third_party_quiche_quiche",
-        "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_brotli_common",
-        "cronet_aml_third_party_brotli_dec",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-        "cronet_aml_third_party_protobuf_protobuf_lite",
-        "cronet_aml_third_party_zlib_zlib",
-        "cronet_aml_url_url",
-    ],
-    generated_headers: [
-        "cronet_aml_net_isolation_info_proto_gen_headers",
-        "cronet_aml_net_net_nqe_proto_gen_headers",
-        "cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen_headers",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DENABLE_BUILT_IN_DNS",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_SYS_UIO_H",
-        "-DNET_IMPLEMENTATION",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "net/third_party/quiche/overrides/",
-        "net/third_party/quiche/src/",
-        "net/third_party/quiche/src/quiche/common/platform/default/",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-        "third_party/brotli/include/",
-        "third_party/protobuf/src/",
-        "third_party/zlib/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_build_branding_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm",
-                "cronet_aml_net_buildflags__android_arm",
-                "cronet_aml_net_net_jni_headers__android_arm",
-                "cronet_aml_url_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_build_branding_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm64",
-                "cronet_aml_net_buildflags__android_arm64",
-                "cronet_aml_net_net_jni_headers__android_arm64",
-                "cronet_aml_url_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_build_branding_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86",
-                "cronet_aml_net_buildflags__android_x86",
-                "cronet_aml_net_net_jni_headers__android_x86",
-                "cronet_aml_url_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_build_branding_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86_64",
-                "cronet_aml_net_buildflags__android_x86_64",
-                "cronet_aml_net_net_jni_headers__android_x86_64",
-                "cronet_aml_url_buildflags__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //net:ios_cronet_buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_net_ios_cronet_buildflags__android_arm",
-    cmd: "echo '--flags CRONET_BUILD=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//net:ios_cronet_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "net/socket/ios_cronet_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net:ios_cronet_buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_net_ios_cronet_buildflags__android_arm64",
-    cmd: "echo '--flags CRONET_BUILD=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//net:ios_cronet_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "net/socket/ios_cronet_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net:ios_cronet_buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_net_ios_cronet_buildflags__android_x86",
-    cmd: "echo '--flags CRONET_BUILD=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//net:ios_cronet_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "net/socket/ios_cronet_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net:ios_cronet_buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_net_ios_cronet_buildflags__android_x86_64",
-    cmd: "echo '--flags CRONET_BUILD=\"false\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//net:ios_cronet_buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "net/socket/ios_cronet_buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net:isolation_info_proto
-cc_genrule {
-    name: "cronet_aml_net_isolation_info_proto_gen",
-    srcs: [
-        "net/base/isolation_info.proto",
-    ],
-    tools: [
-        "cronet_aml_third_party_protobuf_protoc",
-    ],
-    cmd: "$(location cronet_aml_third_party_protobuf_protoc) --proto_path=external/chromium_org/net/base --cpp_out=lite=true:$(genDir)/external/chromium_org/net/base/ $(in)",
-    out: [
-        "external/chromium_org/net/base/isolation_info.pb.cc",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net:isolation_info_proto
-cc_genrule {
-    name: "cronet_aml_net_isolation_info_proto_gen_headers",
-    srcs: [
-        "net/base/isolation_info.proto",
-    ],
-    tools: [
-        "cronet_aml_third_party_protobuf_protoc",
-    ],
-    cmd: "$(location cronet_aml_third_party_protobuf_protoc) --proto_path=external/chromium_org/net/base --cpp_out=lite=true:$(genDir)/external/chromium_org/net/base/ $(in)",
-    out: [
-        "external/chromium_org/net/base/isolation_info.pb.h",
-    ],
-    export_include_dirs: [
-        ".",
-        "net/base",
-        "protos",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net:net
-cc_library_static {
-    name: "cronet_aml_net_net",
-    srcs: [
-        ":cronet_aml_net_dns_dns",
-        ":cronet_aml_net_dns_public_public",
-        ":cronet_aml_net_http_transport_security_state_generated_files",
-        ":cronet_aml_net_net_deps",
-        ":cronet_aml_net_net_public_deps",
-        ":cronet_aml_net_traffic_annotation_traffic_annotation",
-        "net/android/android_http_util.cc",
-        "net/android/cert_verify_result_android.cc",
-        "net/android/gurl_utils.cc",
-        "net/android/http_auth_negotiate_android.cc",
-        "net/android/keystore.cc",
-        "net/android/network_change_notifier_android.cc",
-        "net/android/network_change_notifier_delegate_android.cc",
-        "net/android/network_change_notifier_factory_android.cc",
-        "net/android/network_library.cc",
-        "net/android/radio_activity_tracker.cc",
-        "net/android/traffic_stats.cc",
-        "net/base/address_family.cc",
-        "net/base/address_list.cc",
-        "net/base/address_tracker_linux.cc",
-        "net/base/auth.cc",
-        "net/base/backoff_entry.cc",
-        "net/base/backoff_entry_serializer.cc",
-        "net/base/cache_metrics.cc",
-        "net/base/chunked_upload_data_stream.cc",
-        "net/base/connection_endpoint_metadata.cc",
-        "net/base/data_url.cc",
-        "net/base/datagram_buffer.cc",
-        "net/base/elements_upload_data_stream.cc",
-        "net/base/features.cc",
-        "net/base/file_stream.cc",
-        "net/base/file_stream_context.cc",
-        "net/base/file_stream_context_posix.cc",
-        "net/base/filename_util.cc",
-        "net/base/filename_util_internal.cc",
-        "net/base/hash_value.cc",
-        "net/base/hex_utils.cc",
-        "net/base/host_mapping_rules.cc",
-        "net/base/host_port_pair.cc",
-        "net/base/io_buffer.cc",
-        "net/base/ip_address.cc",
-        "net/base/ip_endpoint.cc",
-        "net/base/isolation_info.cc",
-        "net/base/load_timing_info.cc",
-        "net/base/logging_network_change_observer.cc",
-        "net/base/lookup_string_in_fixed_set.cc",
-        "net/base/mime_sniffer.cc",
-        "net/base/mime_util.cc",
-        "net/base/net_errors.cc",
-        "net/base/net_errors_posix.cc",
-        "net/base/net_module.cc",
-        "net/base/net_string_util_icu_alternatives_android.cc",
-        "net/base/network_activity_monitor.cc",
-        "net/base/network_anonymization_key.cc",
-        "net/base/network_change_notifier.cc",
-        "net/base/network_change_notifier_posix.cc",
-        "net/base/network_delegate.cc",
-        "net/base/network_delegate_impl.cc",
-        "net/base/network_interfaces.cc",
-        "net/base/network_interfaces_getifaddrs.cc",
-        "net/base/network_interfaces_getifaddrs_android.cc",
-        "net/base/network_interfaces_linux.cc",
-        "net/base/network_interfaces_posix.cc",
-        "net/base/network_isolation_key.cc",
-        "net/base/parse_number.cc",
-        "net/base/platform_mime_util_linux.cc",
-        "net/base/port_util.cc",
-        "net/base/prioritized_dispatcher.cc",
-        "net/base/prioritized_task_runner.cc",
-        "net/base/privacy_mode.cc",
-        "net/base/proxy_server.cc",
-        "net/base/proxy_string_util.cc",
-        "net/base/registry_controlled_domains/registry_controlled_domain.cc",
-        "net/base/request_priority.cc",
-        "net/base/scheme_host_port_matcher.cc",
-        "net/base/scheme_host_port_matcher_rule.cc",
-        "net/base/schemeful_site.cc",
-        "net/base/sockaddr_storage.cc",
-        "net/base/sockaddr_util_posix.cc",
-        "net/base/transport_info.cc",
-        "net/base/upload_bytes_element_reader.cc",
-        "net/base/upload_data_stream.cc",
-        "net/base/upload_element_reader.cc",
-        "net/base/upload_file_element_reader.cc",
-        "net/base/url_util.cc",
-        "net/cert/asn1_util.cc",
-        "net/cert/caching_cert_verifier.cc",
-        "net/cert/cert_and_ct_verifier.cc",
-        "net/cert/cert_database.cc",
-        "net/cert/cert_status_flags.cc",
-        "net/cert/cert_verifier.cc",
-        "net/cert/cert_verify_proc.cc",
-        "net/cert/cert_verify_proc_android.cc",
-        "net/cert/cert_verify_proc_builtin.cc",
-        "net/cert/cert_verify_result.cc",
-        "net/cert/coalescing_cert_verifier.cc",
-        "net/cert/crl_set.cc",
-        "net/cert/ct_log_response_parser.cc",
-        "net/cert/ct_log_verifier.cc",
-        "net/cert/ct_log_verifier_util.cc",
-        "net/cert/ct_objects_extractor.cc",
-        "net/cert/ct_policy_enforcer.cc",
-        "net/cert/ct_sct_to_string.cc",
-        "net/cert/ct_serialization.cc",
-        "net/cert/ct_signed_certificate_timestamp_log_param.cc",
-        "net/cert/do_nothing_ct_verifier.cc",
-        "net/cert/ev_root_ca_metadata.cc",
-        "net/cert/internal/cert_issuer_source_aia.cc",
-        "net/cert/internal/revocation_checker.cc",
-        "net/cert/internal/system_trust_store.cc",
-        "net/cert/known_roots.cc",
-        "net/cert/merkle_audit_proof.cc",
-        "net/cert/merkle_consistency_proof.cc",
-        "net/cert/merkle_tree_leaf.cc",
-        "net/cert/multi_log_ct_verifier.cc",
-        "net/cert/multi_threaded_cert_verifier.cc",
-        "net/cert/ocsp_verify_result.cc",
-        "net/cert/pem.cc",
-        "net/cert/pki/cert_error_id.cc",
-        "net/cert/pki/cert_error_params.cc",
-        "net/cert/pki/cert_errors.cc",
-        "net/cert/pki/cert_issuer_source_static.cc",
-        "net/cert/pki/certificate_policies.cc",
-        "net/cert/pki/common_cert_errors.cc",
-        "net/cert/pki/crl.cc",
-        "net/cert/pki/extended_key_usage.cc",
-        "net/cert/pki/general_names.cc",
-        "net/cert/pki/name_constraints.cc",
-        "net/cert/pki/ocsp.cc",
-        "net/cert/pki/parse_certificate.cc",
-        "net/cert/pki/parse_name.cc",
-        "net/cert/pki/parsed_certificate.cc",
-        "net/cert/pki/path_builder.cc",
-        "net/cert/pki/revocation_util.cc",
-        "net/cert/pki/signature_algorithm.cc",
-        "net/cert/pki/simple_path_builder_delegate.cc",
-        "net/cert/pki/string_util.cc",
-        "net/cert/pki/trust_store.cc",
-        "net/cert/pki/trust_store_collection.cc",
-        "net/cert/pki/trust_store_in_memory.cc",
-        "net/cert/pki/verify_certificate_chain.cc",
-        "net/cert/pki/verify_name_match.cc",
-        "net/cert/pki/verify_signed_data.cc",
-        "net/cert/sct_status_flags.cc",
-        "net/cert/signed_certificate_timestamp.cc",
-        "net/cert/signed_certificate_timestamp_and_status.cc",
-        "net/cert/signed_tree_head.cc",
-        "net/cert/symantec_certs.cc",
-        "net/cert/test_root_certs.cc",
-        "net/cert/test_root_certs_android.cc",
-        "net/cert/trial_comparison_cert_verifier_util.cc",
-        "net/cert/x509_cert_types.cc",
-        "net/cert/x509_certificate.cc",
-        "net/cert/x509_certificate_net_log_param.cc",
-        "net/cert/x509_util.cc",
-        "net/cert/x509_util_android.cc",
-        "net/cert_net/cert_net_fetcher_url_request.cc",
-        "net/cookies/canonical_cookie.cc",
-        "net/cookies/cookie_access_delegate.cc",
-        "net/cookies/cookie_access_result.cc",
-        "net/cookies/cookie_change_dispatcher.cc",
-        "net/cookies/cookie_constants.cc",
-        "net/cookies/cookie_deletion_info.cc",
-        "net/cookies/cookie_inclusion_status.cc",
-        "net/cookies/cookie_monster.cc",
-        "net/cookies/cookie_monster_change_dispatcher.cc",
-        "net/cookies/cookie_monster_netlog_params.cc",
-        "net/cookies/cookie_options.cc",
-        "net/cookies/cookie_partition_key.cc",
-        "net/cookies/cookie_partition_key_collection.cc",
-        "net/cookies/cookie_store.cc",
-        "net/cookies/cookie_util.cc",
-        "net/cookies/parsed_cookie.cc",
-        "net/cookies/site_for_cookies.cc",
-        "net/cookies/static_cookie_policy.cc",
-        "net/der/encode_values.cc",
-        "net/der/input.cc",
-        "net/der/parse_values.cc",
-        "net/der/parser.cc",
-        "net/der/tag.cc",
-        "net/disk_cache/backend_cleanup_tracker.cc",
-        "net/disk_cache/blockfile/addr.cc",
-        "net/disk_cache/blockfile/backend_impl.cc",
-        "net/disk_cache/blockfile/bitmap.cc",
-        "net/disk_cache/blockfile/block_files.cc",
-        "net/disk_cache/blockfile/disk_format.cc",
-        "net/disk_cache/blockfile/entry_impl.cc",
-        "net/disk_cache/blockfile/eviction.cc",
-        "net/disk_cache/blockfile/file.cc",
-        "net/disk_cache/blockfile/file_lock.cc",
-        "net/disk_cache/blockfile/file_posix.cc",
-        "net/disk_cache/blockfile/in_flight_backend_io.cc",
-        "net/disk_cache/blockfile/in_flight_io.cc",
-        "net/disk_cache/blockfile/mapped_file.cc",
-        "net/disk_cache/blockfile/rankings.cc",
-        "net/disk_cache/blockfile/sparse_control.cc",
-        "net/disk_cache/blockfile/stats.cc",
-        "net/disk_cache/cache_util.cc",
-        "net/disk_cache/cache_util_posix.cc",
-        "net/disk_cache/disk_cache.cc",
-        "net/disk_cache/memory/mem_backend_impl.cc",
-        "net/disk_cache/memory/mem_entry_impl.cc",
-        "net/disk_cache/net_log_parameters.cc",
-        "net/disk_cache/simple/post_doom_waiter.cc",
-        "net/disk_cache/simple/simple_backend_impl.cc",
-        "net/disk_cache/simple/simple_entry_format.cc",
-        "net/disk_cache/simple/simple_entry_impl.cc",
-        "net/disk_cache/simple/simple_entry_operation.cc",
-        "net/disk_cache/simple/simple_file_enumerator.cc",
-        "net/disk_cache/simple/simple_file_tracker.cc",
-        "net/disk_cache/simple/simple_index.cc",
-        "net/disk_cache/simple/simple_index_file.cc",
-        "net/disk_cache/simple/simple_net_log_parameters.cc",
-        "net/disk_cache/simple/simple_synchronous_entry.cc",
-        "net/disk_cache/simple/simple_util.cc",
-        "net/disk_cache/simple/simple_util_posix.cc",
-        "net/disk_cache/simple/simple_version_upgrade.cc",
-        "net/filter/brotli_source_stream.cc",
-        "net/filter/filter_source_stream.cc",
-        "net/filter/gzip_header.cc",
-        "net/filter/gzip_source_stream.cc",
-        "net/filter/source_stream.cc",
-        "net/first_party_sets/addition_overlaps_union_find.cc",
-        "net/first_party_sets/first_party_set_entry.cc",
-        "net/first_party_sets/first_party_set_metadata.cc",
-        "net/first_party_sets/first_party_sets_cache_filter.cc",
-        "net/first_party_sets/first_party_sets_context_config.cc",
-        "net/first_party_sets/global_first_party_sets.cc",
-        "net/first_party_sets/same_party_context.cc",
-        "net/http/alternative_service.cc",
-        "net/http/bidirectional_stream.cc",
-        "net/http/bidirectional_stream_impl.cc",
-        "net/http/bidirectional_stream_request_info.cc",
-        "net/http/broken_alternative_services.cc",
-        "net/http/http_auth.cc",
-        "net/http/http_auth_cache.cc",
-        "net/http/http_auth_challenge_tokenizer.cc",
-        "net/http/http_auth_controller.cc",
-        "net/http/http_auth_filter.cc",
-        "net/http/http_auth_handler.cc",
-        "net/http/http_auth_handler_basic.cc",
-        "net/http/http_auth_handler_digest.cc",
-        "net/http/http_auth_handler_factory.cc",
-        "net/http/http_auth_handler_negotiate.cc",
-        "net/http/http_auth_handler_ntlm.cc",
-        "net/http/http_auth_handler_ntlm_portable.cc",
-        "net/http/http_auth_multi_round_parse.cc",
-        "net/http/http_auth_ntlm_mechanism.cc",
-        "net/http/http_auth_preferences.cc",
-        "net/http/http_auth_scheme.cc",
-        "net/http/http_basic_state.cc",
-        "net/http/http_basic_stream.cc",
-        "net/http/http_byte_range.cc",
-        "net/http/http_cache.cc",
-        "net/http/http_cache_lookup_manager.cc",
-        "net/http/http_cache_transaction.cc",
-        "net/http/http_cache_writers.cc",
-        "net/http/http_chunked_decoder.cc",
-        "net/http/http_content_disposition.cc",
-        "net/http/http_log_util.cc",
-        "net/http/http_network_layer.cc",
-        "net/http/http_network_session.cc",
-        "net/http/http_network_session_peer.cc",
-        "net/http/http_network_transaction.cc",
-        "net/http/http_proxy_client_socket.cc",
-        "net/http/http_proxy_connect_job.cc",
-        "net/http/http_raw_request_headers.cc",
-        "net/http/http_request_headers.cc",
-        "net/http/http_request_info.cc",
-        "net/http/http_response_body_drainer.cc",
-        "net/http/http_response_headers.cc",
-        "net/http/http_response_info.cc",
-        "net/http/http_security_headers.cc",
-        "net/http/http_server_properties.cc",
-        "net/http/http_server_properties_manager.cc",
-        "net/http/http_status_code.cc",
-        "net/http/http_stream_factory.cc",
-        "net/http/http_stream_factory_job.cc",
-        "net/http/http_stream_factory_job_controller.cc",
-        "net/http/http_stream_parser.cc",
-        "net/http/http_stream_request.cc",
-        "net/http/http_util.cc",
-        "net/http/http_vary_data.cc",
-        "net/http/partial_data.cc",
-        "net/http/proxy_client_socket.cc",
-        "net/http/proxy_fallback.cc",
-        "net/http/transport_security_persister.cc",
-        "net/http/transport_security_state_source.cc",
-        "net/http/url_security_manager.cc",
-        "net/http/url_security_manager_posix.cc",
-        "net/http/webfonts_histogram.cc",
-        "net/log/file_net_log_observer.cc",
-        "net/log/net_log.cc",
-        "net/log/net_log_capture_mode.cc",
-        "net/log/net_log_entry.cc",
-        "net/log/net_log_event_type.cc",
-        "net/log/net_log_source.cc",
-        "net/log/net_log_util.cc",
-        "net/log/net_log_values.cc",
-        "net/log/net_log_with_source.cc",
-        "net/log/trace_net_log_observer.cc",
-        "net/network_error_logging/network_error_logging_service.cc",
-        "net/nqe/cached_network_quality.cc",
-        "net/nqe/effective_connection_type.cc",
-        "net/nqe/event_creator.cc",
-        "net/nqe/network_id.cc",
-        "net/nqe/network_qualities_prefs_manager.cc",
-        "net/nqe/network_quality.cc",
-        "net/nqe/network_quality_estimator.cc",
-        "net/nqe/network_quality_estimator_params.cc",
-        "net/nqe/network_quality_estimator_util.cc",
-        "net/nqe/network_quality_observation.cc",
-        "net/nqe/network_quality_store.cc",
-        "net/nqe/observation_buffer.cc",
-        "net/nqe/pref_names.cc",
-        "net/nqe/socket_watcher.cc",
-        "net/nqe/socket_watcher_factory.cc",
-        "net/nqe/throughput_analyzer.cc",
-        "net/ntlm/ntlm.cc",
-        "net/ntlm/ntlm_buffer_reader.cc",
-        "net/ntlm/ntlm_buffer_writer.cc",
-        "net/ntlm/ntlm_client.cc",
-        "net/ntlm/ntlm_constants.cc",
-        "net/proxy_resolution/configured_proxy_resolution_request.cc",
-        "net/proxy_resolution/configured_proxy_resolution_service.cc",
-        "net/proxy_resolution/dhcp_pac_file_fetcher.cc",
-        "net/proxy_resolution/multi_threaded_proxy_resolver.cc",
-        "net/proxy_resolution/network_delegate_error_observer.cc",
-        "net/proxy_resolution/pac_file_data.cc",
-        "net/proxy_resolution/pac_file_decider.cc",
-        "net/proxy_resolution/pac_file_fetcher.cc",
-        "net/proxy_resolution/pac_file_fetcher_impl.cc",
-        "net/proxy_resolution/polling_proxy_config_service.cc",
-        "net/proxy_resolution/proxy_bypass_rules.cc",
-        "net/proxy_resolution/proxy_config.cc",
-        "net/proxy_resolution/proxy_config_service.cc",
-        "net/proxy_resolution/proxy_config_service_android.cc",
-        "net/proxy_resolution/proxy_config_service_fixed.cc",
-        "net/proxy_resolution/proxy_config_with_annotation.cc",
-        "net/proxy_resolution/proxy_info.cc",
-        "net/proxy_resolution/proxy_list.cc",
-        "net/proxy_resolution/proxy_resolver_factory.cc",
-        "net/quic/bidirectional_stream_quic_impl.cc",
-        "net/quic/crypto/proof_source_chromium.cc",
-        "net/quic/crypto/proof_verifier_chromium.cc",
-        "net/quic/dedicated_web_transport_http3_client.cc",
-        "net/quic/network_connection.cc",
-        "net/quic/platform/impl/quic_chromium_clock.cc",
-        "net/quic/properties_based_quic_server_info.cc",
-        "net/quic/quic_address_mismatch.cc",
-        "net/quic/quic_chromium_alarm_factory.cc",
-        "net/quic/quic_chromium_client_session.cc",
-        "net/quic/quic_chromium_client_stream.cc",
-        "net/quic/quic_chromium_connection_helper.cc",
-        "net/quic/quic_chromium_packet_reader.cc",
-        "net/quic/quic_chromium_packet_writer.cc",
-        "net/quic/quic_clock_skew_detector.cc",
-        "net/quic/quic_connection_logger.cc",
-        "net/quic/quic_connectivity_monitor.cc",
-        "net/quic/quic_context.cc",
-        "net/quic/quic_crypto_client_config_handle.cc",
-        "net/quic/quic_crypto_client_stream_factory.cc",
-        "net/quic/quic_event_logger.cc",
-        "net/quic/quic_http3_logger.cc",
-        "net/quic/quic_http_stream.cc",
-        "net/quic/quic_http_utils.cc",
-        "net/quic/quic_proxy_client_socket.cc",
-        "net/quic/quic_server_info.cc",
-        "net/quic/quic_session_key.cc",
-        "net/quic/quic_stream_factory.cc",
-        "net/quic/set_quic_flag.cc",
-        "net/quic/web_transport_client.cc",
-        "net/quic/web_transport_error.cc",
-        "net/reporting/reporting_browsing_data_remover.cc",
-        "net/reporting/reporting_cache.cc",
-        "net/reporting/reporting_cache_impl.cc",
-        "net/reporting/reporting_cache_observer.cc",
-        "net/reporting/reporting_context.cc",
-        "net/reporting/reporting_delegate.cc",
-        "net/reporting/reporting_delivery_agent.cc",
-        "net/reporting/reporting_endpoint.cc",
-        "net/reporting/reporting_endpoint_manager.cc",
-        "net/reporting/reporting_garbage_collector.cc",
-        "net/reporting/reporting_header_parser.cc",
-        "net/reporting/reporting_network_change_observer.cc",
-        "net/reporting/reporting_policy.cc",
-        "net/reporting/reporting_report.cc",
-        "net/reporting/reporting_service.cc",
-        "net/reporting/reporting_uploader.cc",
-        "net/socket/client_socket_factory.cc",
-        "net/socket/client_socket_handle.cc",
-        "net/socket/client_socket_pool.cc",
-        "net/socket/client_socket_pool_manager.cc",
-        "net/socket/client_socket_pool_manager_impl.cc",
-        "net/socket/connect_job.cc",
-        "net/socket/connect_job_factory.cc",
-        "net/socket/network_binding_client_socket_factory.cc",
-        "net/socket/next_proto.cc",
-        "net/socket/server_socket.cc",
-        "net/socket/socket.cc",
-        "net/socket/socket_bio_adapter.cc",
-        "net/socket/socket_descriptor.cc",
-        "net/socket/socket_net_log_params.cc",
-        "net/socket/socket_options.cc",
-        "net/socket/socket_posix.cc",
-        "net/socket/socket_tag.cc",
-        "net/socket/socks5_client_socket.cc",
-        "net/socket/socks_client_socket.cc",
-        "net/socket/socks_connect_job.cc",
-        "net/socket/ssl_client_socket.cc",
-        "net/socket/ssl_client_socket_impl.cc",
-        "net/socket/ssl_connect_job.cc",
-        "net/socket/ssl_server_socket_impl.cc",
-        "net/socket/stream_socket.cc",
-        "net/socket/tcp_client_socket.cc",
-        "net/socket/tcp_server_socket.cc",
-        "net/socket/tcp_socket_posix.cc",
-        "net/socket/transport_client_socket.cc",
-        "net/socket/transport_client_socket_pool.cc",
-        "net/socket/transport_connect_job.cc",
-        "net/socket/transport_connect_sub_job.cc",
-        "net/socket/udp_client_socket.cc",
-        "net/socket/udp_net_log_parameters.cc",
-        "net/socket/udp_server_socket.cc",
-        "net/socket/udp_socket_global_limits.cc",
-        "net/socket/udp_socket_posix.cc",
-        "net/socket/unix_domain_client_socket_posix.cc",
-        "net/socket/unix_domain_server_socket_posix.cc",
-        "net/socket/websocket_endpoint_lock_manager.cc",
-        "net/socket/websocket_transport_client_socket_pool.cc",
-        "net/spdy/alps_decoder.cc",
-        "net/spdy/bidirectional_stream_spdy_impl.cc",
-        "net/spdy/buffered_spdy_framer.cc",
-        "net/spdy/header_coalescer.cc",
-        "net/spdy/http2_priority_dependencies.cc",
-        "net/spdy/http2_push_promise_index.cc",
-        "net/spdy/multiplexed_http_stream.cc",
-        "net/spdy/multiplexed_session.cc",
-        "net/spdy/spdy_buffer.cc",
-        "net/spdy/spdy_buffer_producer.cc",
-        "net/spdy/spdy_http_stream.cc",
-        "net/spdy/spdy_http_utils.cc",
-        "net/spdy/spdy_log_util.cc",
-        "net/spdy/spdy_proxy_client_socket.cc",
-        "net/spdy/spdy_read_queue.cc",
-        "net/spdy/spdy_session.cc",
-        "net/spdy/spdy_session_key.cc",
-        "net/spdy/spdy_session_pool.cc",
-        "net/spdy/spdy_stream.cc",
-        "net/spdy/spdy_write_queue.cc",
-        "net/ssl/cert_compression.cc",
-        "net/ssl/client_cert_identity.cc",
-        "net/ssl/openssl_ssl_util.cc",
-        "net/ssl/ssl_cert_request_info.cc",
-        "net/ssl/ssl_cipher_suite_names.cc",
-        "net/ssl/ssl_client_auth_cache.cc",
-        "net/ssl/ssl_client_session_cache.cc",
-        "net/ssl/ssl_config.cc",
-        "net/ssl/ssl_config_service.cc",
-        "net/ssl/ssl_config_service_defaults.cc",
-        "net/ssl/ssl_info.cc",
-        "net/ssl/ssl_key_logger.cc",
-        "net/ssl/ssl_key_logger_impl.cc",
-        "net/ssl/ssl_platform_key_android.cc",
-        "net/ssl/ssl_platform_key_util.cc",
-        "net/ssl/ssl_private_key.cc",
-        "net/ssl/ssl_server_config.cc",
-        "net/ssl/threaded_ssl_private_key.cc",
-        "net/url_request/redirect_info.cc",
-        "net/url_request/redirect_util.cc",
-        "net/url_request/report_sender.cc",
-        "net/url_request/static_http_user_agent_settings.cc",
-        "net/url_request/url_request.cc",
-        "net/url_request/url_request_context.cc",
-        "net/url_request/url_request_context_builder.cc",
-        "net/url_request/url_request_context_getter.cc",
-        "net/url_request/url_request_error_job.cc",
-        "net/url_request/url_request_filter.cc",
-        "net/url_request/url_request_http_job.cc",
-        "net/url_request/url_request_interceptor.cc",
-        "net/url_request/url_request_job.cc",
-        "net/url_request/url_request_job_factory.cc",
-        "net/url_request/url_request_netlog_params.cc",
-        "net/url_request/url_request_redirect_job.cc",
-        "net/url_request/url_request_throttler_entry.cc",
-        "net/url_request/url_request_throttler_manager.cc",
-        "net/url_request/view_cache_helper.cc",
-        "net/url_request/websocket_handshake_userdata_key.cc",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_crypto_crypto",
-        "cronet_aml_net_preload_decoder",
-        "cronet_aml_net_third_party_quiche_quiche",
-        "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_brotli_common",
-        "cronet_aml_third_party_brotli_dec",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-        "cronet_aml_third_party_protobuf_protobuf_lite",
-        "cronet_aml_third_party_zlib_zlib",
-        "cronet_aml_url_url",
-    ],
-    generated_headers: [
-        "cronet_aml_net_isolation_info_proto_gen_headers",
-        "cronet_aml_net_net_nqe_proto_gen_headers",
-        "cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen_headers",
-    ],
-    export_generated_headers: [
-        "cronet_aml_net_isolation_info_proto_gen_headers",
-        "cronet_aml_net_net_nqe_proto_gen_headers",
-        "cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen_headers",
-    ],
-    export_static_lib_headers: [
-        "cronet_aml_crypto_crypto",
-        "cronet_aml_net_third_party_quiche_quiche",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DENABLE_BUILT_IN_DNS",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_SYS_UIO_H",
-        "-DNET_IMPLEMENTATION",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "net/third_party/quiche/overrides/",
-        "net/third_party/quiche/src/",
-        "net/third_party/quiche/src/quiche/common/platform/default/",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-        "third_party/brotli/include/",
-        "third_party/protobuf/src/",
-        "third_party/zlib/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            srcs: [
-                "net/disk_cache/blockfile/mapped_file_bypass_mmap_posix.cc",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_build_branding_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm",
-                "cronet_aml_net_buildflags__android_arm",
-                "cronet_aml_net_ios_cronet_buildflags__android_arm",
-                "cronet_aml_net_net_jni_headers__android_arm",
-                "cronet_aml_url_buildflags__android_arm",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_build_branding_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm",
-                "cronet_aml_net_buildflags__android_arm",
-                "cronet_aml_net_ios_cronet_buildflags__android_arm",
-                "cronet_aml_net_net_jni_headers__android_arm",
-                "cronet_aml_url_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            srcs: [
-                "net/disk_cache/blockfile/mapped_file_bypass_mmap_posix.cc",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_build_branding_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm64",
-                "cronet_aml_net_buildflags__android_arm64",
-                "cronet_aml_net_ios_cronet_buildflags__android_arm64",
-                "cronet_aml_net_net_jni_headers__android_arm64",
-                "cronet_aml_url_buildflags__android_arm64",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_build_branding_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm64",
-                "cronet_aml_net_buildflags__android_arm64",
-                "cronet_aml_net_ios_cronet_buildflags__android_arm64",
-                "cronet_aml_net_net_jni_headers__android_arm64",
-                "cronet_aml_url_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            srcs: [
-                "net/disk_cache/blockfile/mapped_file_posix.cc",
-            ],
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_build_branding_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86",
-                "cronet_aml_net_buildflags__android_x86",
-                "cronet_aml_net_ios_cronet_buildflags__android_x86",
-                "cronet_aml_net_net_jni_headers__android_x86",
-                "cronet_aml_url_buildflags__android_x86",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_build_branding_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86",
-                "cronet_aml_net_buildflags__android_x86",
-                "cronet_aml_net_ios_cronet_buildflags__android_x86",
-                "cronet_aml_net_net_jni_headers__android_x86",
-                "cronet_aml_url_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            srcs: [
-                "net/disk_cache/blockfile/mapped_file_bypass_mmap_posix.cc",
-            ],
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_build_branding_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86_64",
-                "cronet_aml_net_buildflags__android_x86_64",
-                "cronet_aml_net_ios_cronet_buildflags__android_x86_64",
-                "cronet_aml_net_net_jni_headers__android_x86_64",
-                "cronet_aml_url_buildflags__android_x86_64",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_build_branding_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86_64",
-                "cronet_aml_net_buildflags__android_x86_64",
-                "cronet_aml_net_ios_cronet_buildflags__android_x86_64",
-                "cronet_aml_net_net_jni_headers__android_x86_64",
-                "cronet_aml_url_buildflags__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //net:net_deps
-cc_object {
-    name: "cronet_aml_net_net_deps",
-    srcs: [
-        ":cronet_aml_net_isolation_info_proto_gen",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-        "libprotobuf-cpp-lite",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_net_preload_decoder",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_brotli_common",
-        "cronet_aml_third_party_brotli_dec",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-        "cronet_aml_third_party_protobuf_protobuf_lite",
-        "cronet_aml_third_party_zlib_zlib",
-    ],
-    generated_headers: [
-        "cronet_aml_net_isolation_info_proto_gen_headers",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DENABLE_BUILT_IN_DNS",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_SYS_UIO_H",
-        "-DNET_IMPLEMENTATION",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-        "third_party/brotli/include/",
-        "third_party/protobuf/src/",
-        "third_party/zlib/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm",
-                "cronet_aml_net_net_jni_headers__android_arm",
-                "cronet_aml_url_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_arm64",
-                "cronet_aml_net_net_jni_headers__android_arm64",
-                "cronet_aml_url_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86",
-                "cronet_aml_net_net_jni_headers__android_x86",
-                "cronet_aml_url_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_net_base_registry_controlled_domains_registry_controlled_domains__android_x86_64",
-                "cronet_aml_net_net_jni_headers__android_x86_64",
-                "cronet_aml_url_buildflags__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //net:net_export_header
-cc_object {
-    name: "cronet_aml_net_net_export_header",
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //net:net_jni_headers__android_arm
-cc_genrule {
-    name: "cronet_aml_net_net_jni_headers__android_arm",
-    srcs: [
-        "net/android/java/src/org/chromium/net/AndroidCertVerifyResult.java",
-        "net/android/java/src/org/chromium/net/AndroidKeyStore.java",
-        "net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java",
-        "net/android/java/src/org/chromium/net/AndroidTrafficStats.java",
-        "net/android/java/src/org/chromium/net/DnsStatus.java",
-        "net/android/java/src/org/chromium/net/GURLUtils.java",
-        "net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java",
-        "net/android/java/src/org/chromium/net/HttpUtil.java",
-        "net/android/java/src/org/chromium/net/NetStringUtil.java",
-        "net/android/java/src/org/chromium/net/NetworkActiveNotifier.java",
-        "net/android/java/src/org/chromium/net/NetworkChangeNotifier.java",
-        "net/android/java/src/org/chromium/net/ProxyChangeListener.java",
-        "net/android/java/src/org/chromium/net/X509Util.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/net/net_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "AndroidCertVerifyResult_jni.h " +
-         "--output_name " +
-         "AndroidKeyStore_jni.h " +
-         "--output_name " +
-         "AndroidNetworkLibrary_jni.h " +
-         "--output_name " +
-         "AndroidTrafficStats_jni.h " +
-         "--output_name " +
-         "DnsStatus_jni.h " +
-         "--output_name " +
-         "GURLUtils_jni.h " +
-         "--output_name " +
-         "HttpNegotiateAuthenticator_jni.h " +
-         "--output_name " +
-         "HttpUtil_jni.h " +
-         "--output_name " +
-         "NetStringUtil_jni.h " +
-         "--output_name " +
-         "NetworkActiveNotifier_jni.h " +
-         "--output_name " +
-         "NetworkChangeNotifier_jni.h " +
-         "--output_name " +
-         "ProxyChangeListener_jni.h " +
-         "--output_name " +
-         "X509Util_jni.h " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/AndroidCertVerifyResult.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/AndroidKeyStore.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/AndroidTrafficStats.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/DnsStatus.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/GURLUtils.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/HttpUtil.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/NetStringUtil.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/NetworkActiveNotifier.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/NetworkChangeNotifier.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/ProxyChangeListener.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/X509Util.java)",
-    out: [
-        "net/net_jni_headers/AndroidCertVerifyResult_jni.h",
-        "net/net_jni_headers/AndroidKeyStore_jni.h",
-        "net/net_jni_headers/AndroidNetworkLibrary_jni.h",
-        "net/net_jni_headers/AndroidTrafficStats_jni.h",
-        "net/net_jni_headers/DnsStatus_jni.h",
-        "net/net_jni_headers/GURLUtils_jni.h",
-        "net/net_jni_headers/HttpNegotiateAuthenticator_jni.h",
-        "net/net_jni_headers/HttpUtil_jni.h",
-        "net/net_jni_headers/NetStringUtil_jni.h",
-        "net/net_jni_headers/NetworkActiveNotifier_jni.h",
-        "net/net_jni_headers/NetworkChangeNotifier_jni.h",
-        "net/net_jni_headers/ProxyChangeListener_jni.h",
-        "net/net_jni_headers/X509Util_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net:net_jni_headers__android_arm64
-cc_genrule {
-    name: "cronet_aml_net_net_jni_headers__android_arm64",
-    srcs: [
-        "net/android/java/src/org/chromium/net/AndroidCertVerifyResult.java",
-        "net/android/java/src/org/chromium/net/AndroidKeyStore.java",
-        "net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java",
-        "net/android/java/src/org/chromium/net/AndroidTrafficStats.java",
-        "net/android/java/src/org/chromium/net/DnsStatus.java",
-        "net/android/java/src/org/chromium/net/GURLUtils.java",
-        "net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java",
-        "net/android/java/src/org/chromium/net/HttpUtil.java",
-        "net/android/java/src/org/chromium/net/NetStringUtil.java",
-        "net/android/java/src/org/chromium/net/NetworkActiveNotifier.java",
-        "net/android/java/src/org/chromium/net/NetworkChangeNotifier.java",
-        "net/android/java/src/org/chromium/net/ProxyChangeListener.java",
-        "net/android/java/src/org/chromium/net/X509Util.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/net/net_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "AndroidCertVerifyResult_jni.h " +
-         "--output_name " +
-         "AndroidKeyStore_jni.h " +
-         "--output_name " +
-         "AndroidNetworkLibrary_jni.h " +
-         "--output_name " +
-         "AndroidTrafficStats_jni.h " +
-         "--output_name " +
-         "DnsStatus_jni.h " +
-         "--output_name " +
-         "GURLUtils_jni.h " +
-         "--output_name " +
-         "HttpNegotiateAuthenticator_jni.h " +
-         "--output_name " +
-         "HttpUtil_jni.h " +
-         "--output_name " +
-         "NetStringUtil_jni.h " +
-         "--output_name " +
-         "NetworkActiveNotifier_jni.h " +
-         "--output_name " +
-         "NetworkChangeNotifier_jni.h " +
-         "--output_name " +
-         "ProxyChangeListener_jni.h " +
-         "--output_name " +
-         "X509Util_jni.h " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/AndroidCertVerifyResult.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/AndroidKeyStore.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/AndroidTrafficStats.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/DnsStatus.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/GURLUtils.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/HttpUtil.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/NetStringUtil.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/NetworkActiveNotifier.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/NetworkChangeNotifier.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/ProxyChangeListener.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/X509Util.java)",
-    out: [
-        "net/net_jni_headers/AndroidCertVerifyResult_jni.h",
-        "net/net_jni_headers/AndroidKeyStore_jni.h",
-        "net/net_jni_headers/AndroidNetworkLibrary_jni.h",
-        "net/net_jni_headers/AndroidTrafficStats_jni.h",
-        "net/net_jni_headers/DnsStatus_jni.h",
-        "net/net_jni_headers/GURLUtils_jni.h",
-        "net/net_jni_headers/HttpNegotiateAuthenticator_jni.h",
-        "net/net_jni_headers/HttpUtil_jni.h",
-        "net/net_jni_headers/NetStringUtil_jni.h",
-        "net/net_jni_headers/NetworkActiveNotifier_jni.h",
-        "net/net_jni_headers/NetworkChangeNotifier_jni.h",
-        "net/net_jni_headers/ProxyChangeListener_jni.h",
-        "net/net_jni_headers/X509Util_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net:net_jni_headers__android_x86
-cc_genrule {
-    name: "cronet_aml_net_net_jni_headers__android_x86",
-    srcs: [
-        "net/android/java/src/org/chromium/net/AndroidCertVerifyResult.java",
-        "net/android/java/src/org/chromium/net/AndroidKeyStore.java",
-        "net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java",
-        "net/android/java/src/org/chromium/net/AndroidTrafficStats.java",
-        "net/android/java/src/org/chromium/net/DnsStatus.java",
-        "net/android/java/src/org/chromium/net/GURLUtils.java",
-        "net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java",
-        "net/android/java/src/org/chromium/net/HttpUtil.java",
-        "net/android/java/src/org/chromium/net/NetStringUtil.java",
-        "net/android/java/src/org/chromium/net/NetworkActiveNotifier.java",
-        "net/android/java/src/org/chromium/net/NetworkChangeNotifier.java",
-        "net/android/java/src/org/chromium/net/ProxyChangeListener.java",
-        "net/android/java/src/org/chromium/net/X509Util.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/net/net_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "AndroidCertVerifyResult_jni.h " +
-         "--output_name " +
-         "AndroidKeyStore_jni.h " +
-         "--output_name " +
-         "AndroidNetworkLibrary_jni.h " +
-         "--output_name " +
-         "AndroidTrafficStats_jni.h " +
-         "--output_name " +
-         "DnsStatus_jni.h " +
-         "--output_name " +
-         "GURLUtils_jni.h " +
-         "--output_name " +
-         "HttpNegotiateAuthenticator_jni.h " +
-         "--output_name " +
-         "HttpUtil_jni.h " +
-         "--output_name " +
-         "NetStringUtil_jni.h " +
-         "--output_name " +
-         "NetworkActiveNotifier_jni.h " +
-         "--output_name " +
-         "NetworkChangeNotifier_jni.h " +
-         "--output_name " +
-         "ProxyChangeListener_jni.h " +
-         "--output_name " +
-         "X509Util_jni.h " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/AndroidCertVerifyResult.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/AndroidKeyStore.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/AndroidTrafficStats.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/DnsStatus.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/GURLUtils.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/HttpUtil.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/NetStringUtil.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/NetworkActiveNotifier.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/NetworkChangeNotifier.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/ProxyChangeListener.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/X509Util.java)",
-    out: [
-        "net/net_jni_headers/AndroidCertVerifyResult_jni.h",
-        "net/net_jni_headers/AndroidKeyStore_jni.h",
-        "net/net_jni_headers/AndroidNetworkLibrary_jni.h",
-        "net/net_jni_headers/AndroidTrafficStats_jni.h",
-        "net/net_jni_headers/DnsStatus_jni.h",
-        "net/net_jni_headers/GURLUtils_jni.h",
-        "net/net_jni_headers/HttpNegotiateAuthenticator_jni.h",
-        "net/net_jni_headers/HttpUtil_jni.h",
-        "net/net_jni_headers/NetStringUtil_jni.h",
-        "net/net_jni_headers/NetworkActiveNotifier_jni.h",
-        "net/net_jni_headers/NetworkChangeNotifier_jni.h",
-        "net/net_jni_headers/ProxyChangeListener_jni.h",
-        "net/net_jni_headers/X509Util_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net:net_jni_headers__android_x86_64
-cc_genrule {
-    name: "cronet_aml_net_net_jni_headers__android_x86_64",
-    srcs: [
-        "net/android/java/src/org/chromium/net/AndroidCertVerifyResult.java",
-        "net/android/java/src/org/chromium/net/AndroidKeyStore.java",
-        "net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java",
-        "net/android/java/src/org/chromium/net/AndroidTrafficStats.java",
-        "net/android/java/src/org/chromium/net/DnsStatus.java",
-        "net/android/java/src/org/chromium/net/GURLUtils.java",
-        "net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java",
-        "net/android/java/src/org/chromium/net/HttpUtil.java",
-        "net/android/java/src/org/chromium/net/NetStringUtil.java",
-        "net/android/java/src/org/chromium/net/NetworkActiveNotifier.java",
-        "net/android/java/src/org/chromium/net/NetworkChangeNotifier.java",
-        "net/android/java/src/org/chromium/net/ProxyChangeListener.java",
-        "net/android/java/src/org/chromium/net/X509Util.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/net/net_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "AndroidCertVerifyResult_jni.h " +
-         "--output_name " +
-         "AndroidKeyStore_jni.h " +
-         "--output_name " +
-         "AndroidNetworkLibrary_jni.h " +
-         "--output_name " +
-         "AndroidTrafficStats_jni.h " +
-         "--output_name " +
-         "DnsStatus_jni.h " +
-         "--output_name " +
-         "GURLUtils_jni.h " +
-         "--output_name " +
-         "HttpNegotiateAuthenticator_jni.h " +
-         "--output_name " +
-         "HttpUtil_jni.h " +
-         "--output_name " +
-         "NetStringUtil_jni.h " +
-         "--output_name " +
-         "NetworkActiveNotifier_jni.h " +
-         "--output_name " +
-         "NetworkChangeNotifier_jni.h " +
-         "--output_name " +
-         "ProxyChangeListener_jni.h " +
-         "--output_name " +
-         "X509Util_jni.h " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/AndroidCertVerifyResult.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/AndroidKeyStore.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/AndroidTrafficStats.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/DnsStatus.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/GURLUtils.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/HttpUtil.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/NetStringUtil.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/NetworkActiveNotifier.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/NetworkChangeNotifier.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/ProxyChangeListener.java) " +
-         "--input_file " +
-         "$(location net/android/java/src/org/chromium/net/X509Util.java)",
-    out: [
-        "net/net_jni_headers/AndroidCertVerifyResult_jni.h",
-        "net/net_jni_headers/AndroidKeyStore_jni.h",
-        "net/net_jni_headers/AndroidNetworkLibrary_jni.h",
-        "net/net_jni_headers/AndroidTrafficStats_jni.h",
-        "net/net_jni_headers/DnsStatus_jni.h",
-        "net/net_jni_headers/GURLUtils_jni.h",
-        "net/net_jni_headers/HttpNegotiateAuthenticator_jni.h",
-        "net/net_jni_headers/HttpUtil_jni.h",
-        "net/net_jni_headers/NetStringUtil_jni.h",
-        "net/net_jni_headers/NetworkActiveNotifier_jni.h",
-        "net/net_jni_headers/NetworkChangeNotifier_jni.h",
-        "net/net_jni_headers/ProxyChangeListener_jni.h",
-        "net/net_jni_headers/X509Util_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net:net_nqe_proto
-cc_genrule {
-    name: "cronet_aml_net_net_nqe_proto_gen",
-    srcs: [
-        "net/nqe/proto/network_id_proto.proto",
-    ],
-    tools: [
-        "cronet_aml_third_party_protobuf_protoc",
-    ],
-    cmd: "$(location cronet_aml_third_party_protobuf_protoc) --proto_path=external/chromium_org/net/nqe/proto --cpp_out=lite=true:$(genDir)/external/chromium_org/net/nqe/proto/ $(in)",
-    out: [
-        "external/chromium_org/net/nqe/proto/network_id_proto.pb.cc",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net:net_nqe_proto
-cc_genrule {
-    name: "cronet_aml_net_net_nqe_proto_gen_headers",
-    srcs: [
-        "net/nqe/proto/network_id_proto.proto",
-    ],
-    tools: [
-        "cronet_aml_third_party_protobuf_protoc",
-    ],
-    cmd: "$(location cronet_aml_third_party_protobuf_protoc) --proto_path=external/chromium_org/net/nqe/proto --cpp_out=lite=true:$(genDir)/external/chromium_org/net/nqe/proto/ $(in)",
-    out: [
-        "external/chromium_org/net/nqe/proto/network_id_proto.pb.h",
-    ],
-    export_include_dirs: [
-        ".",
-        "net/nqe/proto",
-        "protos",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net:net_public_deps
-cc_object {
-    name: "cronet_aml_net_net_public_deps",
-    srcs: [
-        ":cronet_aml_net_net_nqe_proto_gen",
-        ":cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-        "libprotobuf-cpp-lite",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_crypto_crypto",
-        "cronet_aml_net_third_party_quiche_quiche",
-        "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-        "cronet_aml_third_party_protobuf_protobuf_lite",
-        "cronet_aml_third_party_zlib_zlib",
-        "cronet_aml_url_url",
-    ],
-    generated_headers: [
-        "cronet_aml_net_net_nqe_proto_gen_headers",
-        "cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen_headers",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "net/third_party/quiche/overrides/",
-        "net/third_party/quiche/src/",
-        "net/third_party/quiche/src/quiche/common/platform/default/",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-        "third_party/protobuf/src/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_net_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_net_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_net_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_net_buildflags__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //net:preload_decoder
-cc_library_static {
-    name: "cronet_aml_net_preload_decoder",
-    srcs: [
-        "net/extras/preload_data/decoder.cc",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //net/third_party/quiche:net_quic_proto
-cc_genrule {
-    name: "cronet_aml_net_third_party_quiche_net_quic_proto_gen",
-    srcs: [
-        "net/third_party/quiche/src/quiche/quic/core/proto/cached_network_parameters.proto",
-        "net/third_party/quiche/src/quiche/quic/core/proto/crypto_server_config.proto",
-        "net/third_party/quiche/src/quiche/quic/core/proto/source_address_token.proto",
-    ],
-    tools: [
-        "cronet_aml_third_party_protobuf_protoc",
-    ],
-    cmd: "$(location cronet_aml_third_party_protobuf_protoc) --proto_path=external/chromium_org/net/third_party/quiche/src --cpp_out=lite=true:$(genDir)/external/chromium_org/net/third_party/quiche/src/ $(in)",
-    out: [
-        "external/chromium_org/net/third_party/quiche/src/quiche/quic/core/proto/cached_network_parameters.pb.cc",
-        "external/chromium_org/net/third_party/quiche/src/quiche/quic/core/proto/crypto_server_config.pb.cc",
-        "external/chromium_org/net/third_party/quiche/src/quiche/quic/core/proto/source_address_token.pb.cc",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net/third_party/quiche:net_quic_proto
-cc_genrule {
-    name: "cronet_aml_net_third_party_quiche_net_quic_proto_gen_headers",
-    srcs: [
-        "net/third_party/quiche/src/quiche/quic/core/proto/cached_network_parameters.proto",
-        "net/third_party/quiche/src/quiche/quic/core/proto/crypto_server_config.proto",
-        "net/third_party/quiche/src/quiche/quic/core/proto/source_address_token.proto",
-    ],
-    tools: [
-        "cronet_aml_third_party_protobuf_protoc",
-    ],
-    cmd: "$(location cronet_aml_third_party_protobuf_protoc) --proto_path=external/chromium_org/net/third_party/quiche/src --cpp_out=lite=true:$(genDir)/external/chromium_org/net/third_party/quiche/src/ $(in)",
-    out: [
-        "external/chromium_org/net/third_party/quiche/src/quiche/quic/core/proto/cached_network_parameters.pb.h",
-        "external/chromium_org/net/third_party/quiche/src/quiche/quic/core/proto/crypto_server_config.pb.h",
-        "external/chromium_org/net/third_party/quiche/src/quiche/quic/core/proto/source_address_token.pb.h",
-    ],
-    export_include_dirs: [
-        ".",
-        "net/third_party/quiche/src",
-        "protos",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net/third_party/quiche:net_quic_test_tools_proto
-cc_genrule {
-    name: "cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen",
-    srcs: [
-        "net/third_party/quiche/src/quiche/quic/test_tools/send_algorithm_test_result.proto",
-    ],
-    tools: [
-        "cronet_aml_third_party_protobuf_protoc",
-    ],
-    cmd: "$(location cronet_aml_third_party_protobuf_protoc) --proto_path=external/chromium_org/net/third_party/quiche/src/quiche/quic/test_tools --cpp_out=lite=true:$(genDir)/external/chromium_org/net/third_party/quiche/src/quiche/quic/test_tools/ $(in)",
-    out: [
-        "external/chromium_org/net/third_party/quiche/src/quiche/quic/test_tools/send_algorithm_test_result.pb.cc",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net/third_party/quiche:net_quic_test_tools_proto
-cc_genrule {
-    name: "cronet_aml_net_third_party_quiche_net_quic_test_tools_proto_gen_headers",
-    srcs: [
-        "net/third_party/quiche/src/quiche/quic/test_tools/send_algorithm_test_result.proto",
-    ],
-    tools: [
-        "cronet_aml_third_party_protobuf_protoc",
-    ],
-    cmd: "$(location cronet_aml_third_party_protobuf_protoc) --proto_path=external/chromium_org/net/third_party/quiche/src/quiche/quic/test_tools --cpp_out=lite=true:$(genDir)/external/chromium_org/net/third_party/quiche/src/quiche/quic/test_tools/ $(in)",
-    out: [
-        "external/chromium_org/net/third_party/quiche/src/quiche/quic/test_tools/send_algorithm_test_result.pb.h",
-    ],
-    export_include_dirs: [
-        ".",
-        "net/third_party/quiche/src/quiche/quic/test_tools",
-        "protos",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //net/third_party/quiche:quiche
-cc_library_static {
-    name: "cronet_aml_net_third_party_quiche_quiche",
-    srcs: [
-        ":cronet_aml_net_third_party_quiche_net_quic_proto_gen",
-        ":cronet_aml_third_party_abseil_cpp_absl_base_base",
-        ":cronet_aml_third_party_abseil_cpp_absl_base_log_severity",
-        ":cronet_aml_third_party_abseil_cpp_absl_base_malloc_internal",
-        ":cronet_aml_third_party_abseil_cpp_absl_base_raw_logging_internal",
-        ":cronet_aml_third_party_abseil_cpp_absl_base_spinlock_wait",
-        ":cronet_aml_third_party_abseil_cpp_absl_base_strerror",
-        ":cronet_aml_third_party_abseil_cpp_absl_base_throw_delegate",
-        ":cronet_aml_third_party_abseil_cpp_absl_container_hashtablez_sampler",
-        ":cronet_aml_third_party_abseil_cpp_absl_container_raw_hash_set",
-        ":cronet_aml_third_party_abseil_cpp_absl_debugging_debugging_internal",
-        ":cronet_aml_third_party_abseil_cpp_absl_debugging_demangle_internal",
-        ":cronet_aml_third_party_abseil_cpp_absl_debugging_examine_stack",
-        ":cronet_aml_third_party_abseil_cpp_absl_debugging_failure_signal_handler",
-        ":cronet_aml_third_party_abseil_cpp_absl_debugging_stacktrace",
-        ":cronet_aml_third_party_abseil_cpp_absl_debugging_symbolize",
-        ":cronet_aml_third_party_abseil_cpp_absl_hash_city",
-        ":cronet_aml_third_party_abseil_cpp_absl_hash_hash",
-        ":cronet_aml_third_party_abseil_cpp_absl_hash_low_level_hash",
-        ":cronet_aml_third_party_abseil_cpp_absl_numeric_int128",
-        ":cronet_aml_third_party_abseil_cpp_absl_profiling_exponential_biased",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_distributions",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_platform",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_pool_urbg",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_randen",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_randen_hwaes",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_randen_hwaes_impl",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_randen_slow",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_internal_seed_material",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_seed_gen_exception",
-        ":cronet_aml_third_party_abseil_cpp_absl_random_seed_sequences",
-        ":cronet_aml_third_party_abseil_cpp_absl_status_status",
-        ":cronet_aml_third_party_abseil_cpp_absl_status_statusor",
-        ":cronet_aml_third_party_abseil_cpp_absl_strings_cord",
-        ":cronet_aml_third_party_abseil_cpp_absl_strings_cord_internal",
-        ":cronet_aml_third_party_abseil_cpp_absl_strings_cordz_functions",
-        ":cronet_aml_third_party_abseil_cpp_absl_strings_cordz_handle",
-        ":cronet_aml_third_party_abseil_cpp_absl_strings_cordz_info",
-        ":cronet_aml_third_party_abseil_cpp_absl_strings_internal",
-        ":cronet_aml_third_party_abseil_cpp_absl_strings_str_format_internal",
-        ":cronet_aml_third_party_abseil_cpp_absl_strings_strings",
-        ":cronet_aml_third_party_abseil_cpp_absl_synchronization_graphcycles_internal",
-        ":cronet_aml_third_party_abseil_cpp_absl_synchronization_synchronization",
-        ":cronet_aml_third_party_abseil_cpp_absl_time_internal_cctz_civil_time",
-        ":cronet_aml_third_party_abseil_cpp_absl_time_internal_cctz_time_zone",
-        ":cronet_aml_third_party_abseil_cpp_absl_time_time",
-        ":cronet_aml_third_party_abseil_cpp_absl_types_bad_optional_access",
-        ":cronet_aml_third_party_abseil_cpp_absl_types_bad_variant_access",
-        "net/third_party/quiche/overrides/quiche_platform_impl/quiche_mutex_impl.cc",
-        "net/third_party/quiche/overrides/quiche_platform_impl/quiche_time_utils_impl.cc",
-        "net/third_party/quiche/overrides/quiche_platform_impl/quiche_url_utils_impl.cc",
-        "net/third_party/quiche/src/quiche/common/platform/api/quiche_hostname_utils.cc",
-        "net/third_party/quiche/src/quiche/common/platform/api/quiche_mutex.cc",
-        "net/third_party/quiche/src/quiche/common/platform/default/quiche_platform_impl/quiche_flags_impl.cc",
-        "net/third_party/quiche/src/quiche/common/quiche_buffer_allocator.cc",
-        "net/third_party/quiche/src/quiche/common/quiche_crypto_logging.cc",
-        "net/third_party/quiche/src/quiche/common/quiche_data_reader.cc",
-        "net/third_party/quiche/src/quiche/common/quiche_data_writer.cc",
-        "net/third_party/quiche/src/quiche/common/quiche_ip_address.cc",
-        "net/third_party/quiche/src/quiche/common/quiche_ip_address_family.cc",
-        "net/third_party/quiche/src/quiche/common/quiche_mem_slice_storage.cc",
-        "net/third_party/quiche/src/quiche/common/quiche_random.cc",
-        "net/third_party/quiche/src/quiche/common/quiche_text_utils.cc",
-        "net/third_party/quiche/src/quiche/common/simple_buffer_allocator.cc",
-        "net/third_party/quiche/src/quiche/common/structured_headers.cc",
-        "net/third_party/quiche/src/quiche/http2/adapter/event_forwarder.cc",
-        "net/third_party/quiche/src/quiche/http2/adapter/header_validator.cc",
-        "net/third_party/quiche/src/quiche/http2/adapter/http2_protocol.cc",
-        "net/third_party/quiche/src/quiche/http2/adapter/http2_util.cc",
-        "net/third_party/quiche/src/quiche/http2/adapter/noop_header_validator.cc",
-        "net/third_party/quiche/src/quiche/http2/adapter/oghttp2_adapter.cc",
-        "net/third_party/quiche/src/quiche/http2/adapter/oghttp2_session.cc",
-        "net/third_party/quiche/src/quiche/http2/adapter/oghttp2_util.cc",
-        "net/third_party/quiche/src/quiche/http2/adapter/window_manager.cc",
-        "net/third_party/quiche/src/quiche/http2/core/http2_trace_logging.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/decode_buffer.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/decode_http2_structures.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/decode_status.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/frame_decoder_state.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/http2_frame_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/http2_frame_decoder_listener.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/http2_structure_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/altsvc_payload_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/continuation_payload_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/data_payload_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/goaway_payload_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/headers_payload_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/ping_payload_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/priority_payload_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/priority_update_payload_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/push_promise_payload_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/rst_stream_payload_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/settings_payload_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/unknown_payload_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/decoder/payload_decoders/window_update_payload_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_block_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_listener.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_state.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_string_buffer.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoder_tables.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_decoding_error.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_decoder_listener.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_entry_type_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_string_decoder_listener.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_whole_entry_buffer.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/decoder/hpack_whole_entry_listener.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/http2_hpack_constants.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/huffman/hpack_huffman_encoder.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/huffman/huffman_spec_tables.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_decoder.cc",
-        "net/third_party/quiche/src/quiche/http2/hpack/varint/hpack_varint_encoder.cc",
-        "net/third_party/quiche/src/quiche/http2/http2_constants.cc",
-        "net/third_party/quiche/src/quiche/http2/http2_structures.cc",
-        "net/third_party/quiche/src/quiche/quic/core/congestion_control/bandwidth_sampler.cc",
-        "net/third_party/quiche/src/quiche/quic/core/congestion_control/bbr2_drain.cc",
-        "net/third_party/quiche/src/quiche/quic/core/congestion_control/bbr2_misc.cc",
-        "net/third_party/quiche/src/quiche/quic/core/congestion_control/bbr2_probe_bw.cc",
-        "net/third_party/quiche/src/quiche/quic/core/congestion_control/bbr2_probe_rtt.cc",
-        "net/third_party/quiche/src/quiche/quic/core/congestion_control/bbr2_sender.cc",
-        "net/third_party/quiche/src/quiche/quic/core/congestion_control/bbr2_startup.cc",
-        "net/third_party/quiche/src/quiche/quic/core/congestion_control/bbr_sender.cc",
-        "net/third_party/quiche/src/quiche/quic/core/congestion_control/cubic_bytes.cc",
-        "net/third_party/quiche/src/quiche/quic/core/congestion_control/general_loss_algorithm.cc",
-        "net/third_party/quiche/src/quiche/quic/core/congestion_control/hybrid_slow_start.cc",
-        "net/third_party/quiche/src/quiche/quic/core/congestion_control/pacing_sender.cc",
-        "net/third_party/quiche/src/quiche/quic/core/congestion_control/prr_sender.cc",
-        "net/third_party/quiche/src/quiche/quic/core/congestion_control/rtt_stats.cc",
-        "net/third_party/quiche/src/quiche/quic/core/congestion_control/send_algorithm_interface.cc",
-        "net/third_party/quiche/src/quiche/quic/core/congestion_control/tcp_cubic_sender_bytes.cc",
-        "net/third_party/quiche/src/quiche/quic/core/congestion_control/uber_loss_algorithm.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/aead_base_decrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/aead_base_encrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/aes_128_gcm_12_decrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/aes_128_gcm_12_encrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/aes_128_gcm_decrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/aes_128_gcm_encrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/aes_256_gcm_decrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/aes_256_gcm_encrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/aes_base_decrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/aes_base_encrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/cert_compressor.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/certificate_util.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/certificate_view.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/chacha20_poly1305_decrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/chacha20_poly1305_encrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/chacha20_poly1305_tls_decrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/chacha20_poly1305_tls_encrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/chacha_base_decrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/chacha_base_encrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/channel_id.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/client_proof_source.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/crypto_framer.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/crypto_handshake.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/crypto_handshake_message.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/crypto_secret_boxer.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/crypto_utils.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/curve25519_key_exchange.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/key_exchange.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/null_decrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/null_encrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/p256_key_exchange.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/proof_source.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/proof_source_x509.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/quic_client_session_cache.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/quic_compressed_certs_cache.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/quic_crypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/quic_crypto_client_config.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/quic_crypto_proof.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/quic_crypto_server_config.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/quic_decrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/quic_encrypter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/quic_hkdf.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/tls_client_connection.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/tls_connection.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/tls_server_connection.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/transport_parameters.cc",
-        "net/third_party/quiche/src/quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier.cc",
-        "net/third_party/quiche/src/quiche/quic/core/deterministic_connection_id_generator.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_ack_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_ack_frequency_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_blocked_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_connection_close_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_crypto_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_goaway_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_handshake_done_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_max_streams_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_message_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_new_connection_id_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_new_token_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_padding_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_path_challenge_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_path_response_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_ping_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_retire_connection_id_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_rst_stream_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_stop_sending_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_stop_waiting_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_stream_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_streams_blocked_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/frames/quic_window_update_frame.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/capsule.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/http_constants.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/http_decoder.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/http_encoder.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/quic_client_promised_info.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/quic_client_push_promise_index.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/quic_header_list.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/quic_headers_stream.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/quic_receive_control_stream.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/quic_send_control_stream.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/quic_server_initiated_spdy_stream.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/quic_server_session_base.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_client_session.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_client_session_base.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_client_stream.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_session.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_stream.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/quic_spdy_stream_body_manager.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/spdy_server_push_utils.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/spdy_utils.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/web_transport_http3.cc",
-        "net/third_party/quiche/src/quiche/quic/core/http/web_transport_stream_adapter.cc",
-        "net/third_party/quiche/src/quiche/quic/core/legacy_quic_stream_id_manager.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_blocking_manager.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_decoded_headers_accumulator.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_decoder.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_decoder_stream_receiver.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_decoder_stream_sender.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_encoder.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_encoder_stream_receiver.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_encoder_stream_sender.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_header_table.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_index_conversions.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_instruction_decoder.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_instruction_encoder.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_instructions.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_progressive_decoder.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_receive_stream.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_required_insert_count.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_send_stream.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/qpack_static_table.cc",
-        "net/third_party/quiche/src/quiche/quic/core/qpack/value_splitting_header_list.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_ack_listener_interface.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_alarm.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_bandwidth.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_chaos_protector.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_clock.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_coalesced_packet.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_config.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_connection.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_connection_context.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_connection_id.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_connection_id_manager.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_connection_stats.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_constants.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_control_frame_manager.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_crypto_client_handshaker.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_crypto_client_stream.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_crypto_handshaker.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_crypto_server_stream.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_crypto_server_stream_base.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_crypto_stream.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_data_reader.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_data_writer.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_datagram_queue.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_error_codes.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_flow_controller.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_framer.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_idle_network_detector.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_mtu_discovery.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_network_blackhole_detector.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_packet_creator.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_packet_number.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_packets.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_path_validator.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_ping_manager.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_received_packet_manager.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_sent_packet_manager.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_server_id.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_session.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_socket_address_coder.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_stream.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_stream_id_manager.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_stream_send_buffer.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_stream_sequencer.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_stream_sequencer_buffer.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_sustained_bandwidth_recorder.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_tag.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_time.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_transmission_info.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_types.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_unacked_packet_map.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_utils.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_version_manager.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_versions.cc",
-        "net/third_party/quiche/src/quiche/quic/core/quic_write_blocked_list.cc",
-        "net/third_party/quiche/src/quiche/quic/core/tls_client_handshaker.cc",
-        "net/third_party/quiche/src/quiche/quic/core/tls_handshaker.cc",
-        "net/third_party/quiche/src/quiche/quic/core/tls_server_handshaker.cc",
-        "net/third_party/quiche/src/quiche/quic/core/uber_quic_stream_id_manager.cc",
-        "net/third_party/quiche/src/quiche/quic/core/uber_received_packet_manager.cc",
-        "net/third_party/quiche/src/quiche/quic/platform/api/quic_socket_address.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/array_output_buffer.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_constants.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_decoder_adapter.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_encoder.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_entry.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_header_table.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_output_stream.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/hpack/hpack_static_table.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/http2_frame_decoder_adapter.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/http2_header_block.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/http2_header_storage.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/recording_headers_handler.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/spdy_alt_svc_wire_format.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/spdy_frame_builder.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/spdy_framer.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/spdy_no_op_visitor.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/spdy_pinnable_buffer_piece.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/spdy_prefixed_buffer_reader.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/spdy_protocol.cc",
-        "net/third_party/quiche/src/quiche/spdy/core/spdy_simple_arena.cc",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-        "libprotobuf-cpp-lite",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_net_uri_template",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-        "cronet_aml_third_party_protobuf_protobuf_lite",
-        "cronet_aml_third_party_zlib_zlib",
-        "cronet_aml_url_url",
-    ],
-    generated_headers: [
-        "cronet_aml_net_third_party_quiche_net_quic_proto_gen_headers",
-    ],
-    export_generated_headers: [
-        "cronet_aml_net_third_party_quiche_net_quic_proto_gen_headers",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_SYS_UIO_H",
-        "-DIS_QUICHE_IMPL",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "net/third_party/quiche/overrides/",
-        "net/third_party/quiche/src/",
-        "net/third_party/quiche/src/quiche/common/platform/default/",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-        "third_party/protobuf/src/",
-        "third_party/zlib/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-            ],
-            export_generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-            ],
-            export_generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-            ],
-            export_generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-            ],
-            export_generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //net/traffic_annotation:traffic_annotation
-cc_object {
-    name: "cronet_aml_net_traffic_annotation_traffic_annotation",
-    srcs: [
-        "net/traffic_annotation/network_traffic_annotation_android.cc",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //net:uri_template
-cc_library_static {
-    name: "cronet_aml_net_uri_template",
-    srcs: [
-        "net/third_party/uri_template/uri_template.cc",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-DIS_URI_TEMPLATE_IMPL",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp:absl
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__host",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/algorithm:algorithm
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_algorithm_algorithm",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/algorithm:container
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_algorithm_container",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/base:atomic_hook
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_base_atomic_hook",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/base:base
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_base_base",
-    srcs: [
-        "third_party/abseil-cpp/absl/base/internal/cycleclock.cc",
-        "third_party/abseil-cpp/absl/base/internal/spinlock.cc",
-        "third_party/abseil-cpp/absl/base/internal/sysinfo.cc",
-        "third_party/abseil-cpp/absl/base/internal/thread_identity.cc",
-        "third_party/abseil-cpp/absl/base/internal/unscaledcycleclock.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/base:base_internal
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_base_base_internal",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/base:config
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_base_config",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/base:core_headers
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_base_core_headers",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/base:cycleclock_internal
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_base_cycleclock_internal",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/base:dynamic_annotations
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_base_dynamic_annotations",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/base:endian
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_base_endian",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/base:errno_saver
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_base_errno_saver",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/base:fast_type_id
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_base_fast_type_id",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/base:log_severity
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_base_log_severity",
-    srcs: [
-        "third_party/abseil-cpp/absl/base/log_severity.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/base:malloc_internal
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_base_malloc_internal",
-    srcs: [
-        "third_party/abseil-cpp/absl/base/internal/low_level_alloc.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/base:prefetch
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_base_prefetch",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/base:raw_logging_internal
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_base_raw_logging_internal",
-    srcs: [
-        "third_party/abseil-cpp/absl/base/internal/raw_logging.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/base:spinlock_wait
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_base_spinlock_wait",
-    srcs: [
-        "third_party/abseil-cpp/absl/base/internal/spinlock_wait.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/base:strerror
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_base_strerror",
-    srcs: [
-        "third_party/abseil-cpp/absl/base/internal/strerror.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/base:throw_delegate
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_base_throw_delegate",
-    srcs: [
-        "third_party/abseil-cpp/absl/base/internal/throw_delegate.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/cleanup:cleanup
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_cleanup_cleanup",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/cleanup:cleanup_internal
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_cleanup_cleanup_internal",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:btree
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_btree",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:common
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_common",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:common_policy_traits
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_common_policy_traits",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:compressed_tuple
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_compressed_tuple",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:container_memory
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_container_memory",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:fixed_array
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_fixed_array",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:flat_hash_map
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_flat_hash_map",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:flat_hash_set
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_flat_hash_set",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:hash_function_defaults
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_hash_function_defaults",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:hash_policy_traits
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_hash_policy_traits",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:hashtable_debug_hooks
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_hashtable_debug_hooks",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:hashtablez_sampler
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_hashtablez_sampler",
-    srcs: [
-        "third_party/abseil-cpp/absl/container/internal/hashtablez_sampler.cc",
-        "third_party/abseil-cpp/absl/container/internal/hashtablez_sampler_force_weak_definition.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:inlined_vector
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_inlined_vector",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:inlined_vector_internal
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_inlined_vector_internal",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:layout
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_layout",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:node_hash_map
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_node_hash_map",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:node_hash_set
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_node_hash_set",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:node_slot_policy
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_node_slot_policy",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:raw_hash_map
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_raw_hash_map",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/container:raw_hash_set
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_container_raw_hash_set",
-    srcs: [
-        "third_party/abseil-cpp/absl/container/internal/raw_hash_set.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/debugging:debugging_internal
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_debugging_debugging_internal",
-    srcs: [
-        "third_party/abseil-cpp/absl/debugging/internal/address_is_readable.cc",
-        "third_party/abseil-cpp/absl/debugging/internal/elf_mem_image.cc",
-        "third_party/abseil-cpp/absl/debugging/internal/vdso_support.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/debugging:demangle_internal
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_debugging_demangle_internal",
-    srcs: [
-        "third_party/abseil-cpp/absl/debugging/internal/demangle.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/debugging:examine_stack
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_debugging_examine_stack",
-    srcs: [
-        "third_party/abseil-cpp/absl/debugging/internal/examine_stack.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/debugging:failure_signal_handler
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_debugging_failure_signal_handler",
-    srcs: [
-        "third_party/abseil-cpp/absl/debugging/failure_signal_handler.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/debugging:stacktrace
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_debugging_stacktrace",
-    srcs: [
-        "third_party/abseil-cpp/absl/debugging/stacktrace.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/debugging:symbolize
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_debugging_symbolize",
-    srcs: [
-        "third_party/abseil-cpp/absl/debugging/symbolize.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/functional:any_invocable
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_functional_any_invocable",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/functional:bind_front
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_functional_bind_front",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/functional:function_ref
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_functional_function_ref",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/hash:city
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_hash_city",
-    srcs: [
-        "third_party/abseil-cpp/absl/hash/internal/city.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/hash:hash
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_hash_hash",
-    srcs: [
-        "third_party/abseil-cpp/absl/hash/internal/hash.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/hash:low_level_hash
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_hash_low_level_hash",
-    srcs: [
-        "third_party/abseil-cpp/absl/hash/internal/low_level_hash.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/memory:memory
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_memory_memory",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/meta:type_traits
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_meta_type_traits",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/numeric:bits
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_numeric_bits",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/numeric:int128
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_numeric_int128",
-    srcs: [
-        "third_party/abseil-cpp/absl/numeric/int128.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/numeric:representation
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_numeric_representation",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/profiling:exponential_biased
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_profiling_exponential_biased",
-    srcs: [
-        "third_party/abseil-cpp/absl/profiling/internal/exponential_biased.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/profiling:sample_recorder
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_profiling_sample_recorder",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random:distributions
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_distributions",
-    srcs: [
-        "third_party/abseil-cpp/absl/random/discrete_distribution.cc",
-        "third_party/abseil-cpp/absl/random/gaussian_distribution.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:distribution_caller
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_distribution_caller",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:fast_uniform_bits
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_fast_uniform_bits",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:fastmath
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_fastmath",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:generate_real
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_generate_real",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:iostream_state_saver
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_iostream_state_saver",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:nonsecure_base
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_nonsecure_base",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__host",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:pcg_engine
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_pcg_engine",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:platform
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_platform",
-    srcs: [
-        "third_party/abseil-cpp/absl/random/internal/randen_round_keys.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__host",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:pool_urbg
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_pool_urbg",
-    srcs: [
-        "third_party/abseil-cpp/absl/random/internal/pool_urbg.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__host",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:randen
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_randen",
-    srcs: [
-        "third_party/abseil-cpp/absl/random/internal/randen.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__host",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:randen_engine
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_randen_engine",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__host",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:randen_hwaes
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_randen_hwaes",
-    srcs: [
-        "third_party/abseil-cpp/absl/random/internal/randen_detect.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__host",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:randen_hwaes_impl
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_randen_hwaes_impl",
-    srcs: [
-        "third_party/abseil-cpp/absl/random/internal/randen_hwaes.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__host",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:randen_slow
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_randen_slow",
-    srcs: [
-        "third_party/abseil-cpp/absl/random/internal/randen_slow.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__host",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:salted_seed_seq
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_salted_seed_seq",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:seed_material
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_seed_material",
-    srcs: [
-        "third_party/abseil-cpp/absl/random/internal/seed_material.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:traits
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_traits",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:uniform_helper
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_uniform_helper",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random/internal:wide_multiply
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_internal_wide_multiply",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random:random
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_random",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__host",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random:seed_gen_exception
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_seed_gen_exception",
-    srcs: [
-        "third_party/abseil-cpp/absl/random/seed_gen_exception.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/random:seed_sequences
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_random_seed_sequences",
-    srcs: [
-        "third_party/abseil-cpp/absl/random/seed_sequences.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_build_chromeos_buildflags__host",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/status:status
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_status_status",
-    srcs: [
-        "third_party/abseil-cpp/absl/status/status.cc",
-        "third_party/abseil-cpp/absl/status/status_payload_printer.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/status:statusor
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_status_statusor",
-    srcs: [
-        "third_party/abseil-cpp/absl/status/statusor.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/strings:cord
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_strings_cord",
-    srcs: [
-        "third_party/abseil-cpp/absl/strings/cord.cc",
-        "third_party/abseil-cpp/absl/strings/cord_analysis.cc",
-        "third_party/abseil-cpp/absl/strings/cord_buffer.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/strings:cord_internal
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_strings_cord_internal",
-    srcs: [
-        "third_party/abseil-cpp/absl/strings/internal/cord_internal.cc",
-        "third_party/abseil-cpp/absl/strings/internal/cord_rep_btree.cc",
-        "third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_navigator.cc",
-        "third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_reader.cc",
-        "third_party/abseil-cpp/absl/strings/internal/cord_rep_consume.cc",
-        "third_party/abseil-cpp/absl/strings/internal/cord_rep_crc.cc",
-        "third_party/abseil-cpp/absl/strings/internal/cord_rep_ring.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/strings:cordz_functions
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_strings_cordz_functions",
-    srcs: [
-        "third_party/abseil-cpp/absl/strings/internal/cordz_functions.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/strings:cordz_handle
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_strings_cordz_handle",
-    srcs: [
-        "third_party/abseil-cpp/absl/strings/internal/cordz_handle.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/strings:cordz_info
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_strings_cordz_info",
-    srcs: [
-        "third_party/abseil-cpp/absl/strings/internal/cordz_info.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/strings:cordz_statistics
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_strings_cordz_statistics",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/strings:cordz_update_scope
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_strings_cordz_update_scope",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/strings:cordz_update_tracker
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_strings_cordz_update_tracker",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/strings:internal
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_strings_internal",
-    srcs: [
-        "third_party/abseil-cpp/absl/strings/internal/escaping.cc",
-        "third_party/abseil-cpp/absl/strings/internal/ostringstream.cc",
-        "third_party/abseil-cpp/absl/strings/internal/utf8.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/strings:str_format
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_strings_str_format",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/strings:str_format_internal
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_strings_str_format_internal",
-    srcs: [
-        "third_party/abseil-cpp/absl/strings/internal/str_format/arg.cc",
-        "third_party/abseil-cpp/absl/strings/internal/str_format/bind.cc",
-        "third_party/abseil-cpp/absl/strings/internal/str_format/extension.cc",
-        "third_party/abseil-cpp/absl/strings/internal/str_format/float_conversion.cc",
-        "third_party/abseil-cpp/absl/strings/internal/str_format/output.cc",
-        "third_party/abseil-cpp/absl/strings/internal/str_format/parser.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/strings:strings
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_strings_strings",
-    srcs: [
-        "third_party/abseil-cpp/absl/strings/ascii.cc",
-        "third_party/abseil-cpp/absl/strings/charconv.cc",
-        "third_party/abseil-cpp/absl/strings/escaping.cc",
-        "third_party/abseil-cpp/absl/strings/internal/charconv_bigint.cc",
-        "third_party/abseil-cpp/absl/strings/internal/charconv_parse.cc",
-        "third_party/abseil-cpp/absl/strings/internal/damerau_levenshtein_distance.cc",
-        "third_party/abseil-cpp/absl/strings/internal/memutil.cc",
-        "third_party/abseil-cpp/absl/strings/match.cc",
-        "third_party/abseil-cpp/absl/strings/numbers.cc",
-        "third_party/abseil-cpp/absl/strings/str_cat.cc",
-        "third_party/abseil-cpp/absl/strings/str_replace.cc",
-        "third_party/abseil-cpp/absl/strings/str_split.cc",
-        "third_party/abseil-cpp/absl/strings/string_view.cc",
-        "third_party/abseil-cpp/absl/strings/substitute.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/synchronization:graphcycles_internal
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_synchronization_graphcycles_internal",
-    srcs: [
-        "third_party/abseil-cpp/absl/synchronization/internal/graphcycles.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/synchronization:kernel_timeout_internal
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_synchronization_kernel_timeout_internal",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/synchronization:synchronization
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_synchronization_synchronization",
-    srcs: [
-        "third_party/abseil-cpp/absl/synchronization/barrier.cc",
-        "third_party/abseil-cpp/absl/synchronization/blocking_counter.cc",
-        "third_party/abseil-cpp/absl/synchronization/internal/create_thread_identity.cc",
-        "third_party/abseil-cpp/absl/synchronization/internal/per_thread_sem.cc",
-        "third_party/abseil-cpp/absl/synchronization/internal/waiter.cc",
-        "third_party/abseil-cpp/absl/synchronization/mutex.cc",
-        "third_party/abseil-cpp/absl/synchronization/notification.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/time/internal/cctz:civil_time
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_time_internal_cctz_civil_time",
-    srcs: [
-        "third_party/abseil-cpp/absl/time/internal/cctz/src/civil_time_detail.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/time/internal/cctz:time_zone
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_time_internal_cctz_time_zone",
-    srcs: [
-        "third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_fixed.cc",
-        "third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_format.cc",
-        "third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_if.cc",
-        "third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_impl.cc",
-        "third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_info.cc",
-        "third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_libc.cc",
-        "third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_lookup.cc",
-        "third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_posix.cc",
-        "third_party/abseil-cpp/absl/time/internal/cctz/src/zone_info_source.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/time:time
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_time_time",
-    srcs: [
-        "third_party/abseil-cpp/absl/time/civil_time.cc",
-        "third_party/abseil-cpp/absl/time/clock.cc",
-        "third_party/abseil-cpp/absl/time/duration.cc",
-        "third_party/abseil-cpp/absl/time/format.cc",
-        "third_party/abseil-cpp/absl/time/time.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/types:bad_optional_access
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_types_bad_optional_access",
-    srcs: [
-        "third_party/abseil-cpp/absl/types/bad_optional_access.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/types:bad_variant_access
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_types_bad_variant_access",
-    srcs: [
-        "third_party/abseil-cpp/absl/types/bad_variant_access.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/types:compare
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_types_compare",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/types:optional
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_types_optional",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/types:span
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_types_span",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/types:variant
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_types_variant",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/abseil-cpp/absl/utility:utility
-cc_object {
-    name: "cronet_aml_third_party_abseil_cpp_absl_utility_utility",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DABSL_ALLOCATOR_NOTHROW=1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/android_ndk:cpu_features
-cc_object {
-    name: "cronet_aml_third_party_android_ndk_cpu_features",
-    srcs: [
-        "third_party/android_ndk/sources/android/cpufeatures/cpu-features.c",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/android_ndk/sources/android/cpufeatures/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/ashmem:ashmem
-cc_object {
-    name: "cronet_aml_third_party_ashmem_ashmem",
-    srcs: [
-        "third_party/ashmem/ashmem-dev.c",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/boringssl:boringssl
-cc_library_static {
-    name: "cronet_aml_third_party_boringssl_boringssl",
-    srcs: [
-        ":cronet_aml_third_party_boringssl_boringssl_asm",
-        "third_party/boringssl/err_data.c",
-        "third_party/boringssl/src/crypto/asn1/a_bitstr.c",
-        "third_party/boringssl/src/crypto/asn1/a_bool.c",
-        "third_party/boringssl/src/crypto/asn1/a_d2i_fp.c",
-        "third_party/boringssl/src/crypto/asn1/a_dup.c",
-        "third_party/boringssl/src/crypto/asn1/a_gentm.c",
-        "third_party/boringssl/src/crypto/asn1/a_i2d_fp.c",
-        "third_party/boringssl/src/crypto/asn1/a_int.c",
-        "third_party/boringssl/src/crypto/asn1/a_mbstr.c",
-        "third_party/boringssl/src/crypto/asn1/a_object.c",
-        "third_party/boringssl/src/crypto/asn1/a_octet.c",
-        "third_party/boringssl/src/crypto/asn1/a_print.c",
-        "third_party/boringssl/src/crypto/asn1/a_strex.c",
-        "third_party/boringssl/src/crypto/asn1/a_strnid.c",
-        "third_party/boringssl/src/crypto/asn1/a_time.c",
-        "third_party/boringssl/src/crypto/asn1/a_type.c",
-        "third_party/boringssl/src/crypto/asn1/a_utctm.c",
-        "third_party/boringssl/src/crypto/asn1/a_utf8.c",
-        "third_party/boringssl/src/crypto/asn1/asn1_lib.c",
-        "third_party/boringssl/src/crypto/asn1/asn1_par.c",
-        "third_party/boringssl/src/crypto/asn1/asn_pack.c",
-        "third_party/boringssl/src/crypto/asn1/f_int.c",
-        "third_party/boringssl/src/crypto/asn1/f_string.c",
-        "third_party/boringssl/src/crypto/asn1/posix_time.c",
-        "third_party/boringssl/src/crypto/asn1/tasn_dec.c",
-        "third_party/boringssl/src/crypto/asn1/tasn_enc.c",
-        "third_party/boringssl/src/crypto/asn1/tasn_fre.c",
-        "third_party/boringssl/src/crypto/asn1/tasn_new.c",
-        "third_party/boringssl/src/crypto/asn1/tasn_typ.c",
-        "third_party/boringssl/src/crypto/asn1/tasn_utl.c",
-        "third_party/boringssl/src/crypto/base64/base64.c",
-        "third_party/boringssl/src/crypto/bio/bio.c",
-        "third_party/boringssl/src/crypto/bio/bio_mem.c",
-        "third_party/boringssl/src/crypto/bio/connect.c",
-        "third_party/boringssl/src/crypto/bio/fd.c",
-        "third_party/boringssl/src/crypto/bio/file.c",
-        "third_party/boringssl/src/crypto/bio/hexdump.c",
-        "third_party/boringssl/src/crypto/bio/pair.c",
-        "third_party/boringssl/src/crypto/bio/printf.c",
-        "third_party/boringssl/src/crypto/bio/socket.c",
-        "third_party/boringssl/src/crypto/bio/socket_helper.c",
-        "third_party/boringssl/src/crypto/blake2/blake2.c",
-        "third_party/boringssl/src/crypto/bn_extra/bn_asn1.c",
-        "third_party/boringssl/src/crypto/bn_extra/convert.c",
-        "third_party/boringssl/src/crypto/buf/buf.c",
-        "third_party/boringssl/src/crypto/bytestring/asn1_compat.c",
-        "third_party/boringssl/src/crypto/bytestring/ber.c",
-        "third_party/boringssl/src/crypto/bytestring/cbb.c",
-        "third_party/boringssl/src/crypto/bytestring/cbs.c",
-        "third_party/boringssl/src/crypto/bytestring/unicode.c",
-        "third_party/boringssl/src/crypto/chacha/chacha.c",
-        "third_party/boringssl/src/crypto/cipher_extra/cipher_extra.c",
-        "third_party/boringssl/src/crypto/cipher_extra/derive_key.c",
-        "third_party/boringssl/src/crypto/cipher_extra/e_aesctrhmac.c",
-        "third_party/boringssl/src/crypto/cipher_extra/e_aesgcmsiv.c",
-        "third_party/boringssl/src/crypto/cipher_extra/e_chacha20poly1305.c",
-        "third_party/boringssl/src/crypto/cipher_extra/e_des.c",
-        "third_party/boringssl/src/crypto/cipher_extra/e_null.c",
-        "third_party/boringssl/src/crypto/cipher_extra/e_rc2.c",
-        "third_party/boringssl/src/crypto/cipher_extra/e_rc4.c",
-        "third_party/boringssl/src/crypto/cipher_extra/e_tls.c",
-        "third_party/boringssl/src/crypto/cipher_extra/tls_cbc.c",
-        "third_party/boringssl/src/crypto/conf/conf.c",
-        "third_party/boringssl/src/crypto/cpu_aarch64_apple.c",
-        "third_party/boringssl/src/crypto/cpu_aarch64_fuchsia.c",
-        "third_party/boringssl/src/crypto/cpu_aarch64_linux.c",
-        "third_party/boringssl/src/crypto/cpu_aarch64_win.c",
-        "third_party/boringssl/src/crypto/cpu_arm.c",
-        "third_party/boringssl/src/crypto/cpu_arm_linux.c",
-        "third_party/boringssl/src/crypto/cpu_intel.c",
-        "third_party/boringssl/src/crypto/cpu_ppc64le.c",
-        "third_party/boringssl/src/crypto/crypto.c",
-        "third_party/boringssl/src/crypto/curve25519/curve25519.c",
-        "third_party/boringssl/src/crypto/curve25519/spake25519.c",
-        "third_party/boringssl/src/crypto/des/des.c",
-        "third_party/boringssl/src/crypto/dh_extra/dh_asn1.c",
-        "third_party/boringssl/src/crypto/dh_extra/params.c",
-        "third_party/boringssl/src/crypto/digest_extra/digest_extra.c",
-        "third_party/boringssl/src/crypto/dsa/dsa.c",
-        "third_party/boringssl/src/crypto/dsa/dsa_asn1.c",
-        "third_party/boringssl/src/crypto/ec_extra/ec_asn1.c",
-        "third_party/boringssl/src/crypto/ec_extra/ec_derive.c",
-        "third_party/boringssl/src/crypto/ec_extra/hash_to_curve.c",
-        "third_party/boringssl/src/crypto/ecdh_extra/ecdh_extra.c",
-        "third_party/boringssl/src/crypto/ecdsa_extra/ecdsa_asn1.c",
-        "third_party/boringssl/src/crypto/engine/engine.c",
-        "third_party/boringssl/src/crypto/err/err.c",
-        "third_party/boringssl/src/crypto/evp/evp.c",
-        "third_party/boringssl/src/crypto/evp/evp_asn1.c",
-        "third_party/boringssl/src/crypto/evp/evp_ctx.c",
-        "third_party/boringssl/src/crypto/evp/p_dsa_asn1.c",
-        "third_party/boringssl/src/crypto/evp/p_ec.c",
-        "third_party/boringssl/src/crypto/evp/p_ec_asn1.c",
-        "third_party/boringssl/src/crypto/evp/p_ed25519.c",
-        "third_party/boringssl/src/crypto/evp/p_ed25519_asn1.c",
-        "third_party/boringssl/src/crypto/evp/p_hkdf.c",
-        "third_party/boringssl/src/crypto/evp/p_rsa.c",
-        "third_party/boringssl/src/crypto/evp/p_rsa_asn1.c",
-        "third_party/boringssl/src/crypto/evp/p_x25519.c",
-        "third_party/boringssl/src/crypto/evp/p_x25519_asn1.c",
-        "third_party/boringssl/src/crypto/evp/pbkdf.c",
-        "third_party/boringssl/src/crypto/evp/print.c",
-        "third_party/boringssl/src/crypto/evp/scrypt.c",
-        "third_party/boringssl/src/crypto/evp/sign.c",
-        "third_party/boringssl/src/crypto/ex_data.c",
-        "third_party/boringssl/src/crypto/fipsmodule/bcm.c",
-        "third_party/boringssl/src/crypto/fipsmodule/fips_shared_support.c",
-        "third_party/boringssl/src/crypto/hkdf/hkdf.c",
-        "third_party/boringssl/src/crypto/hpke/hpke.c",
-        "third_party/boringssl/src/crypto/hrss/hrss.c",
-        "third_party/boringssl/src/crypto/lhash/lhash.c",
-        "third_party/boringssl/src/crypto/mem.c",
-        "third_party/boringssl/src/crypto/obj/obj.c",
-        "third_party/boringssl/src/crypto/obj/obj_xref.c",
-        "third_party/boringssl/src/crypto/pem/pem_all.c",
-        "third_party/boringssl/src/crypto/pem/pem_info.c",
-        "third_party/boringssl/src/crypto/pem/pem_lib.c",
-        "third_party/boringssl/src/crypto/pem/pem_oth.c",
-        "third_party/boringssl/src/crypto/pem/pem_pk8.c",
-        "third_party/boringssl/src/crypto/pem/pem_pkey.c",
-        "third_party/boringssl/src/crypto/pem/pem_x509.c",
-        "third_party/boringssl/src/crypto/pem/pem_xaux.c",
-        "third_party/boringssl/src/crypto/pkcs7/pkcs7.c",
-        "third_party/boringssl/src/crypto/pkcs7/pkcs7_x509.c",
-        "third_party/boringssl/src/crypto/pkcs8/p5_pbev2.c",
-        "third_party/boringssl/src/crypto/pkcs8/pkcs8.c",
-        "third_party/boringssl/src/crypto/pkcs8/pkcs8_x509.c",
-        "third_party/boringssl/src/crypto/poly1305/poly1305.c",
-        "third_party/boringssl/src/crypto/poly1305/poly1305_arm.c",
-        "third_party/boringssl/src/crypto/poly1305/poly1305_vec.c",
-        "third_party/boringssl/src/crypto/pool/pool.c",
-        "third_party/boringssl/src/crypto/rand_extra/deterministic.c",
-        "third_party/boringssl/src/crypto/rand_extra/forkunsafe.c",
-        "third_party/boringssl/src/crypto/rand_extra/fuchsia.c",
-        "third_party/boringssl/src/crypto/rand_extra/passive.c",
-        "third_party/boringssl/src/crypto/rand_extra/rand_extra.c",
-        "third_party/boringssl/src/crypto/rand_extra/windows.c",
-        "third_party/boringssl/src/crypto/rc4/rc4.c",
-        "third_party/boringssl/src/crypto/refcount_c11.c",
-        "third_party/boringssl/src/crypto/refcount_lock.c",
-        "third_party/boringssl/src/crypto/rsa_extra/rsa_asn1.c",
-        "third_party/boringssl/src/crypto/rsa_extra/rsa_print.c",
-        "third_party/boringssl/src/crypto/siphash/siphash.c",
-        "third_party/boringssl/src/crypto/stack/stack.c",
-        "third_party/boringssl/src/crypto/thread.c",
-        "third_party/boringssl/src/crypto/thread_none.c",
-        "third_party/boringssl/src/crypto/thread_pthread.c",
-        "third_party/boringssl/src/crypto/thread_win.c",
-        "third_party/boringssl/src/crypto/trust_token/pmbtoken.c",
-        "third_party/boringssl/src/crypto/trust_token/trust_token.c",
-        "third_party/boringssl/src/crypto/trust_token/voprf.c",
-        "third_party/boringssl/src/crypto/x509/a_digest.c",
-        "third_party/boringssl/src/crypto/x509/a_sign.c",
-        "third_party/boringssl/src/crypto/x509/a_verify.c",
-        "third_party/boringssl/src/crypto/x509/algorithm.c",
-        "third_party/boringssl/src/crypto/x509/asn1_gen.c",
-        "third_party/boringssl/src/crypto/x509/by_dir.c",
-        "third_party/boringssl/src/crypto/x509/by_file.c",
-        "third_party/boringssl/src/crypto/x509/i2d_pr.c",
-        "third_party/boringssl/src/crypto/x509/name_print.c",
-        "third_party/boringssl/src/crypto/x509/rsa_pss.c",
-        "third_party/boringssl/src/crypto/x509/t_crl.c",
-        "third_party/boringssl/src/crypto/x509/t_req.c",
-        "third_party/boringssl/src/crypto/x509/t_x509.c",
-        "third_party/boringssl/src/crypto/x509/t_x509a.c",
-        "third_party/boringssl/src/crypto/x509/x509.c",
-        "third_party/boringssl/src/crypto/x509/x509_att.c",
-        "third_party/boringssl/src/crypto/x509/x509_cmp.c",
-        "third_party/boringssl/src/crypto/x509/x509_d2.c",
-        "third_party/boringssl/src/crypto/x509/x509_def.c",
-        "third_party/boringssl/src/crypto/x509/x509_ext.c",
-        "third_party/boringssl/src/crypto/x509/x509_lu.c",
-        "third_party/boringssl/src/crypto/x509/x509_obj.c",
-        "third_party/boringssl/src/crypto/x509/x509_req.c",
-        "third_party/boringssl/src/crypto/x509/x509_set.c",
-        "third_party/boringssl/src/crypto/x509/x509_trs.c",
-        "third_party/boringssl/src/crypto/x509/x509_txt.c",
-        "third_party/boringssl/src/crypto/x509/x509_v3.c",
-        "third_party/boringssl/src/crypto/x509/x509_vfy.c",
-        "third_party/boringssl/src/crypto/x509/x509_vpm.c",
-        "third_party/boringssl/src/crypto/x509/x509cset.c",
-        "third_party/boringssl/src/crypto/x509/x509name.c",
-        "third_party/boringssl/src/crypto/x509/x509rset.c",
-        "third_party/boringssl/src/crypto/x509/x509spki.c",
-        "third_party/boringssl/src/crypto/x509/x_algor.c",
-        "third_party/boringssl/src/crypto/x509/x_all.c",
-        "third_party/boringssl/src/crypto/x509/x_attrib.c",
-        "third_party/boringssl/src/crypto/x509/x_crl.c",
-        "third_party/boringssl/src/crypto/x509/x_exten.c",
-        "third_party/boringssl/src/crypto/x509/x_info.c",
-        "third_party/boringssl/src/crypto/x509/x_name.c",
-        "third_party/boringssl/src/crypto/x509/x_pkey.c",
-        "third_party/boringssl/src/crypto/x509/x_pubkey.c",
-        "third_party/boringssl/src/crypto/x509/x_req.c",
-        "third_party/boringssl/src/crypto/x509/x_sig.c",
-        "third_party/boringssl/src/crypto/x509/x_spki.c",
-        "third_party/boringssl/src/crypto/x509/x_val.c",
-        "third_party/boringssl/src/crypto/x509/x_x509.c",
-        "third_party/boringssl/src/crypto/x509/x_x509a.c",
-        "third_party/boringssl/src/crypto/x509v3/pcy_cache.c",
-        "third_party/boringssl/src/crypto/x509v3/pcy_data.c",
-        "third_party/boringssl/src/crypto/x509v3/pcy_map.c",
-        "third_party/boringssl/src/crypto/x509v3/pcy_node.c",
-        "third_party/boringssl/src/crypto/x509v3/pcy_tree.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_akey.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_akeya.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_alt.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_bcons.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_bitst.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_conf.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_cpols.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_crld.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_enum.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_extku.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_genn.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_ia5.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_info.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_int.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_lib.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_ncons.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_ocsp.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_pci.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_pcia.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_pcons.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_pmaps.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_prn.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_purp.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_skey.c",
-        "third_party/boringssl/src/crypto/x509v3/v3_utl.c",
-        "third_party/boringssl/src/ssl/bio_ssl.cc",
-        "third_party/boringssl/src/ssl/d1_both.cc",
-        "third_party/boringssl/src/ssl/d1_lib.cc",
-        "third_party/boringssl/src/ssl/d1_pkt.cc",
-        "third_party/boringssl/src/ssl/d1_srtp.cc",
-        "third_party/boringssl/src/ssl/dtls_method.cc",
-        "third_party/boringssl/src/ssl/dtls_record.cc",
-        "third_party/boringssl/src/ssl/encrypted_client_hello.cc",
-        "third_party/boringssl/src/ssl/extensions.cc",
-        "third_party/boringssl/src/ssl/handoff.cc",
-        "third_party/boringssl/src/ssl/handshake.cc",
-        "third_party/boringssl/src/ssl/handshake_client.cc",
-        "third_party/boringssl/src/ssl/handshake_server.cc",
-        "third_party/boringssl/src/ssl/s3_both.cc",
-        "third_party/boringssl/src/ssl/s3_lib.cc",
-        "third_party/boringssl/src/ssl/s3_pkt.cc",
-        "third_party/boringssl/src/ssl/ssl_aead_ctx.cc",
-        "third_party/boringssl/src/ssl/ssl_asn1.cc",
-        "third_party/boringssl/src/ssl/ssl_buffer.cc",
-        "third_party/boringssl/src/ssl/ssl_cert.cc",
-        "third_party/boringssl/src/ssl/ssl_cipher.cc",
-        "third_party/boringssl/src/ssl/ssl_file.cc",
-        "third_party/boringssl/src/ssl/ssl_key_share.cc",
-        "third_party/boringssl/src/ssl/ssl_lib.cc",
-        "third_party/boringssl/src/ssl/ssl_privkey.cc",
-        "third_party/boringssl/src/ssl/ssl_session.cc",
-        "third_party/boringssl/src/ssl/ssl_stat.cc",
-        "third_party/boringssl/src/ssl/ssl_transcript.cc",
-        "third_party/boringssl/src/ssl/ssl_versions.cc",
-        "third_party/boringssl/src/ssl/ssl_x509.cc",
-        "third_party/boringssl/src/ssl/t1_enc.cc",
-        "third_party/boringssl/src/ssl/tls13_both.cc",
-        "third_party/boringssl/src/ssl/tls13_client.cc",
-        "third_party/boringssl/src/ssl/tls13_enc.cc",
-        "third_party/boringssl/src/ssl/tls13_server.cc",
-        "third_party/boringssl/src/ssl/tls_method.cc",
-        "third_party/boringssl/src/ssl/tls_record.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DBORINGSSL_ALLOW_CXX_RUNTIME",
-        "-DBORINGSSL_IMPLEMENTATION",
-        "-DBORINGSSL_NO_STATIC_INITIALIZER",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DOPENSSL_SMALL",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/boringssl/src/include/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/boringssl:boringssl_asm
-cc_object {
-    name: "cronet_aml_third_party_boringssl_boringssl_asm",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/boringssl/src/include/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            srcs: [
-                "third_party/boringssl/linux-arm/crypto/chacha/chacha-armv4.S",
-                "third_party/boringssl/linux-arm/crypto/fipsmodule/aesv8-armx32.S",
-                "third_party/boringssl/linux-arm/crypto/fipsmodule/armv4-mont.S",
-                "third_party/boringssl/linux-arm/crypto/fipsmodule/bsaes-armv7.S",
-                "third_party/boringssl/linux-arm/crypto/fipsmodule/ghash-armv4.S",
-                "third_party/boringssl/linux-arm/crypto/fipsmodule/ghashv8-armx32.S",
-                "third_party/boringssl/linux-arm/crypto/fipsmodule/sha1-armv4-large.S",
-                "third_party/boringssl/linux-arm/crypto/fipsmodule/sha256-armv4.S",
-                "third_party/boringssl/linux-arm/crypto/fipsmodule/sha512-armv4.S",
-                "third_party/boringssl/linux-arm/crypto/fipsmodule/vpaes-armv7.S",
-                "third_party/boringssl/linux-arm/crypto/test/trampoline-armv4.S",
-                "third_party/boringssl/src/crypto/curve25519/asm/x25519-asm-arm.S",
-                "third_party/boringssl/src/crypto/poly1305/poly1305_arm_asm.S",
-            ],
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            srcs: [
-                "third_party/boringssl/linux-aarch64/crypto/chacha/chacha-armv8.S",
-                "third_party/boringssl/linux-aarch64/crypto/cipher_extra/chacha20_poly1305_armv8.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/aesv8-armx64.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/armv8-mont.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/ghash-neon-armv8.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/ghashv8-armx64.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/p256-armv8-asm.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/p256_beeu-armv8-asm.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/sha1-armv8.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/sha256-armv8.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/sha512-armv8.S",
-                "third_party/boringssl/linux-aarch64/crypto/fipsmodule/vpaes-armv8.S",
-                "third_party/boringssl/linux-aarch64/crypto/test/trampoline-armv8.S",
-            ],
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            srcs: [
-                "third_party/boringssl/linux-x86/crypto/chacha/chacha-x86.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/aesni-x86.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/bn-586.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/co-586.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/ghash-ssse3-x86.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/ghash-x86.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/md5-586.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/sha1-586.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/sha256-586.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/sha512-586.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/vpaes-x86.S",
-                "third_party/boringssl/linux-x86/crypto/fipsmodule/x86-mont.S",
-                "third_party/boringssl/linux-x86/crypto/test/trampoline-x86.S",
-            ],
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            srcs: [
-                "third_party/boringssl/linux-x86_64/crypto/chacha/chacha-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/cipher_extra/chacha20_poly1305_x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/aesni-gcm-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/aesni-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/ghash-ssse3-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/ghash-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/md5-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/p256-x86_64-asm.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/rdrand-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/rsaz-avx2.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/sha1-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/sha256-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/sha512-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/vpaes-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/x86_64-mont.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/x86_64-mont5.S",
-                "third_party/boringssl/linux-x86_64/crypto/test/trampoline-x86_64.S",
-                "third_party/boringssl/src/crypto/hrss/asm/poly_rq_mul.S",
-            ],
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            srcs: [
-                "third_party/boringssl/linux-x86_64/crypto/chacha/chacha-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/cipher_extra/chacha20_poly1305_x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/aesni-gcm-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/aesni-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/ghash-ssse3-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/ghash-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/md5-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/p256-x86_64-asm.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/rdrand-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/rsaz-avx2.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/sha1-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/sha256-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/sha512-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/vpaes-x86_64.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/x86_64-mont.S",
-                "third_party/boringssl/linux-x86_64/crypto/fipsmodule/x86_64-mont5.S",
-                "third_party/boringssl/linux-x86_64/crypto/test/trampoline-x86_64.S",
-                "third_party/boringssl/src/crypto/hrss/asm/poly_rq_mul.S",
-            ],
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/boringssl/src/third_party/fiat:fiat_license
-cc_object {
-    name: "cronet_aml_third_party_boringssl_src_third_party_fiat_fiat_license",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/brotli:common
-cc_library_static {
-    name: "cronet_aml_third_party_brotli_common",
-    srcs: [
-        "third_party/brotli/common/constants.c",
-        "third_party/brotli/common/context.c",
-        "third_party/brotli/common/dictionary.c",
-        "third_party/brotli/common/platform.c",
-        "third_party/brotli/common/shared_dictionary.c",
-        "third_party/brotli/common/transform.c",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/brotli/include/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/brotli:dec
-cc_library_static {
-    name: "cronet_aml_third_party_brotli_dec",
-    srcs: [
-        "third_party/brotli/dec/bit_reader.c",
-        "third_party/brotli/dec/decode.c",
-        "third_party/brotli/dec/huffman.c",
-        "third_party/brotli/dec/state.c",
-    ],
-    static_libs: [
-        "cronet_aml_third_party_brotli_common",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/brotli/include/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/brotli:headers
-cc_object {
-    name: "cronet_aml_third_party_brotli_headers",
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/icu:icui18n
-cc_library_static {
-    name: "cronet_aml_third_party_icu_icui18n",
-    srcs: [
-        "third_party/icu/source/i18n/alphaindex.cpp",
-        "third_party/icu/source/i18n/anytrans.cpp",
-        "third_party/icu/source/i18n/astro.cpp",
-        "third_party/icu/source/i18n/basictz.cpp",
-        "third_party/icu/source/i18n/bocsu.cpp",
-        "third_party/icu/source/i18n/brktrans.cpp",
-        "third_party/icu/source/i18n/buddhcal.cpp",
-        "third_party/icu/source/i18n/calendar.cpp",
-        "third_party/icu/source/i18n/casetrn.cpp",
-        "third_party/icu/source/i18n/cecal.cpp",
-        "third_party/icu/source/i18n/chnsecal.cpp",
-        "third_party/icu/source/i18n/choicfmt.cpp",
-        "third_party/icu/source/i18n/coleitr.cpp",
-        "third_party/icu/source/i18n/coll.cpp",
-        "third_party/icu/source/i18n/collation.cpp",
-        "third_party/icu/source/i18n/collationbuilder.cpp",
-        "third_party/icu/source/i18n/collationcompare.cpp",
-        "third_party/icu/source/i18n/collationdata.cpp",
-        "third_party/icu/source/i18n/collationdatabuilder.cpp",
-        "third_party/icu/source/i18n/collationdatareader.cpp",
-        "third_party/icu/source/i18n/collationdatawriter.cpp",
-        "third_party/icu/source/i18n/collationfastlatin.cpp",
-        "third_party/icu/source/i18n/collationfastlatinbuilder.cpp",
-        "third_party/icu/source/i18n/collationfcd.cpp",
-        "third_party/icu/source/i18n/collationiterator.cpp",
-        "third_party/icu/source/i18n/collationkeys.cpp",
-        "third_party/icu/source/i18n/collationroot.cpp",
-        "third_party/icu/source/i18n/collationrootelements.cpp",
-        "third_party/icu/source/i18n/collationruleparser.cpp",
-        "third_party/icu/source/i18n/collationsets.cpp",
-        "third_party/icu/source/i18n/collationsettings.cpp",
-        "third_party/icu/source/i18n/collationtailoring.cpp",
-        "third_party/icu/source/i18n/collationweights.cpp",
-        "third_party/icu/source/i18n/compactdecimalformat.cpp",
-        "third_party/icu/source/i18n/coptccal.cpp",
-        "third_party/icu/source/i18n/cpdtrans.cpp",
-        "third_party/icu/source/i18n/csdetect.cpp",
-        "third_party/icu/source/i18n/csmatch.cpp",
-        "third_party/icu/source/i18n/csr2022.cpp",
-        "third_party/icu/source/i18n/csrecog.cpp",
-        "third_party/icu/source/i18n/csrmbcs.cpp",
-        "third_party/icu/source/i18n/csrsbcs.cpp",
-        "third_party/icu/source/i18n/csrucode.cpp",
-        "third_party/icu/source/i18n/csrutf8.cpp",
-        "third_party/icu/source/i18n/curramt.cpp",
-        "third_party/icu/source/i18n/currfmt.cpp",
-        "third_party/icu/source/i18n/currpinf.cpp",
-        "third_party/icu/source/i18n/currunit.cpp",
-        "third_party/icu/source/i18n/dangical.cpp",
-        "third_party/icu/source/i18n/datefmt.cpp",
-        "third_party/icu/source/i18n/dayperiodrules.cpp",
-        "third_party/icu/source/i18n/dcfmtsym.cpp",
-        "third_party/icu/source/i18n/decContext.cpp",
-        "third_party/icu/source/i18n/decNumber.cpp",
-        "third_party/icu/source/i18n/decimfmt.cpp",
-        "third_party/icu/source/i18n/double-conversion-bignum-dtoa.cpp",
-        "third_party/icu/source/i18n/double-conversion-bignum.cpp",
-        "third_party/icu/source/i18n/double-conversion-cached-powers.cpp",
-        "third_party/icu/source/i18n/double-conversion-double-to-string.cpp",
-        "third_party/icu/source/i18n/double-conversion-fast-dtoa.cpp",
-        "third_party/icu/source/i18n/double-conversion-string-to-double.cpp",
-        "third_party/icu/source/i18n/double-conversion-strtod.cpp",
-        "third_party/icu/source/i18n/dtfmtsym.cpp",
-        "third_party/icu/source/i18n/dtitvfmt.cpp",
-        "third_party/icu/source/i18n/dtitvinf.cpp",
-        "third_party/icu/source/i18n/dtptngen.cpp",
-        "third_party/icu/source/i18n/dtrule.cpp",
-        "third_party/icu/source/i18n/erarules.cpp",
-        "third_party/icu/source/i18n/esctrn.cpp",
-        "third_party/icu/source/i18n/ethpccal.cpp",
-        "third_party/icu/source/i18n/fmtable.cpp",
-        "third_party/icu/source/i18n/fmtable_cnv.cpp",
-        "third_party/icu/source/i18n/format.cpp",
-        "third_party/icu/source/i18n/formatted_string_builder.cpp",
-        "third_party/icu/source/i18n/formattedval_iterimpl.cpp",
-        "third_party/icu/source/i18n/formattedval_sbimpl.cpp",
-        "third_party/icu/source/i18n/formattedvalue.cpp",
-        "third_party/icu/source/i18n/fphdlimp.cpp",
-        "third_party/icu/source/i18n/fpositer.cpp",
-        "third_party/icu/source/i18n/funcrepl.cpp",
-        "third_party/icu/source/i18n/gender.cpp",
-        "third_party/icu/source/i18n/gregocal.cpp",
-        "third_party/icu/source/i18n/gregoimp.cpp",
-        "third_party/icu/source/i18n/hebrwcal.cpp",
-        "third_party/icu/source/i18n/indiancal.cpp",
-        "third_party/icu/source/i18n/inputext.cpp",
-        "third_party/icu/source/i18n/islamcal.cpp",
-        "third_party/icu/source/i18n/japancal.cpp",
-        "third_party/icu/source/i18n/listformatter.cpp",
-        "third_party/icu/source/i18n/measfmt.cpp",
-        "third_party/icu/source/i18n/measunit.cpp",
-        "third_party/icu/source/i18n/measunit_extra.cpp",
-        "third_party/icu/source/i18n/measure.cpp",
-        "third_party/icu/source/i18n/msgfmt.cpp",
-        "third_party/icu/source/i18n/name2uni.cpp",
-        "third_party/icu/source/i18n/nfrs.cpp",
-        "third_party/icu/source/i18n/nfrule.cpp",
-        "third_party/icu/source/i18n/nfsubs.cpp",
-        "third_party/icu/source/i18n/nortrans.cpp",
-        "third_party/icu/source/i18n/nultrans.cpp",
-        "third_party/icu/source/i18n/number_affixutils.cpp",
-        "third_party/icu/source/i18n/number_asformat.cpp",
-        "third_party/icu/source/i18n/number_capi.cpp",
-        "third_party/icu/source/i18n/number_compact.cpp",
-        "third_party/icu/source/i18n/number_currencysymbols.cpp",
-        "third_party/icu/source/i18n/number_decimalquantity.cpp",
-        "third_party/icu/source/i18n/number_decimfmtprops.cpp",
-        "third_party/icu/source/i18n/number_fluent.cpp",
-        "third_party/icu/source/i18n/number_formatimpl.cpp",
-        "third_party/icu/source/i18n/number_grouping.cpp",
-        "third_party/icu/source/i18n/number_integerwidth.cpp",
-        "third_party/icu/source/i18n/number_longnames.cpp",
-        "third_party/icu/source/i18n/number_mapper.cpp",
-        "third_party/icu/source/i18n/number_modifiers.cpp",
-        "third_party/icu/source/i18n/number_multiplier.cpp",
-        "third_party/icu/source/i18n/number_notation.cpp",
-        "third_party/icu/source/i18n/number_output.cpp",
-        "third_party/icu/source/i18n/number_padding.cpp",
-        "third_party/icu/source/i18n/number_patternmodifier.cpp",
-        "third_party/icu/source/i18n/number_patternstring.cpp",
-        "third_party/icu/source/i18n/number_rounding.cpp",
-        "third_party/icu/source/i18n/number_scientific.cpp",
-        "third_party/icu/source/i18n/number_skeletons.cpp",
-        "third_party/icu/source/i18n/number_symbolswrapper.cpp",
-        "third_party/icu/source/i18n/number_usageprefs.cpp",
-        "third_party/icu/source/i18n/number_utils.cpp",
-        "third_party/icu/source/i18n/numfmt.cpp",
-        "third_party/icu/source/i18n/numparse_affixes.cpp",
-        "third_party/icu/source/i18n/numparse_compositions.cpp",
-        "third_party/icu/source/i18n/numparse_currency.cpp",
-        "third_party/icu/source/i18n/numparse_decimal.cpp",
-        "third_party/icu/source/i18n/numparse_impl.cpp",
-        "third_party/icu/source/i18n/numparse_parsednumber.cpp",
-        "third_party/icu/source/i18n/numparse_scientific.cpp",
-        "third_party/icu/source/i18n/numparse_symbols.cpp",
-        "third_party/icu/source/i18n/numparse_validators.cpp",
-        "third_party/icu/source/i18n/numrange_capi.cpp",
-        "third_party/icu/source/i18n/numrange_fluent.cpp",
-        "third_party/icu/source/i18n/numrange_impl.cpp",
-        "third_party/icu/source/i18n/numsys.cpp",
-        "third_party/icu/source/i18n/olsontz.cpp",
-        "third_party/icu/source/i18n/persncal.cpp",
-        "third_party/icu/source/i18n/pluralranges.cpp",
-        "third_party/icu/source/i18n/plurfmt.cpp",
-        "third_party/icu/source/i18n/plurrule.cpp",
-        "third_party/icu/source/i18n/quant.cpp",
-        "third_party/icu/source/i18n/quantityformatter.cpp",
-        "third_party/icu/source/i18n/rbnf.cpp",
-        "third_party/icu/source/i18n/rbt.cpp",
-        "third_party/icu/source/i18n/rbt_data.cpp",
-        "third_party/icu/source/i18n/rbt_pars.cpp",
-        "third_party/icu/source/i18n/rbt_rule.cpp",
-        "third_party/icu/source/i18n/rbt_set.cpp",
-        "third_party/icu/source/i18n/rbtz.cpp",
-        "third_party/icu/source/i18n/regexcmp.cpp",
-        "third_party/icu/source/i18n/regeximp.cpp",
-        "third_party/icu/source/i18n/regexst.cpp",
-        "third_party/icu/source/i18n/regextxt.cpp",
-        "third_party/icu/source/i18n/region.cpp",
-        "third_party/icu/source/i18n/reldatefmt.cpp",
-        "third_party/icu/source/i18n/reldtfmt.cpp",
-        "third_party/icu/source/i18n/rematch.cpp",
-        "third_party/icu/source/i18n/remtrans.cpp",
-        "third_party/icu/source/i18n/repattrn.cpp",
-        "third_party/icu/source/i18n/rulebasedcollator.cpp",
-        "third_party/icu/source/i18n/scientificnumberformatter.cpp",
-        "third_party/icu/source/i18n/scriptset.cpp",
-        "third_party/icu/source/i18n/search.cpp",
-        "third_party/icu/source/i18n/selfmt.cpp",
-        "third_party/icu/source/i18n/sharedbreakiterator.cpp",
-        "third_party/icu/source/i18n/simpletz.cpp",
-        "third_party/icu/source/i18n/smpdtfmt.cpp",
-        "third_party/icu/source/i18n/smpdtfst.cpp",
-        "third_party/icu/source/i18n/sortkey.cpp",
-        "third_party/icu/source/i18n/standardplural.cpp",
-        "third_party/icu/source/i18n/string_segment.cpp",
-        "third_party/icu/source/i18n/strmatch.cpp",
-        "third_party/icu/source/i18n/strrepl.cpp",
-        "third_party/icu/source/i18n/stsearch.cpp",
-        "third_party/icu/source/i18n/taiwncal.cpp",
-        "third_party/icu/source/i18n/timezone.cpp",
-        "third_party/icu/source/i18n/titletrn.cpp",
-        "third_party/icu/source/i18n/tmunit.cpp",
-        "third_party/icu/source/i18n/tmutamt.cpp",
-        "third_party/icu/source/i18n/tmutfmt.cpp",
-        "third_party/icu/source/i18n/tolowtrn.cpp",
-        "third_party/icu/source/i18n/toupptrn.cpp",
-        "third_party/icu/source/i18n/translit.cpp",
-        "third_party/icu/source/i18n/transreg.cpp",
-        "third_party/icu/source/i18n/tridpars.cpp",
-        "third_party/icu/source/i18n/tzfmt.cpp",
-        "third_party/icu/source/i18n/tzgnames.cpp",
-        "third_party/icu/source/i18n/tznames.cpp",
-        "third_party/icu/source/i18n/tznames_impl.cpp",
-        "third_party/icu/source/i18n/tzrule.cpp",
-        "third_party/icu/source/i18n/tztrans.cpp",
-        "third_party/icu/source/i18n/ucal.cpp",
-        "third_party/icu/source/i18n/ucln_in.cpp",
-        "third_party/icu/source/i18n/ucol.cpp",
-        "third_party/icu/source/i18n/ucol_res.cpp",
-        "third_party/icu/source/i18n/ucol_sit.cpp",
-        "third_party/icu/source/i18n/ucoleitr.cpp",
-        "third_party/icu/source/i18n/ucsdet.cpp",
-        "third_party/icu/source/i18n/udat.cpp",
-        "third_party/icu/source/i18n/udateintervalformat.cpp",
-        "third_party/icu/source/i18n/udatpg.cpp",
-        "third_party/icu/source/i18n/ufieldpositer.cpp",
-        "third_party/icu/source/i18n/uitercollationiterator.cpp",
-        "third_party/icu/source/i18n/ulistformatter.cpp",
-        "third_party/icu/source/i18n/ulocdata.cpp",
-        "third_party/icu/source/i18n/umsg.cpp",
-        "third_party/icu/source/i18n/unesctrn.cpp",
-        "third_party/icu/source/i18n/uni2name.cpp",
-        "third_party/icu/source/i18n/units_complexconverter.cpp",
-        "third_party/icu/source/i18n/units_converter.cpp",
-        "third_party/icu/source/i18n/units_data.cpp",
-        "third_party/icu/source/i18n/units_router.cpp",
-        "third_party/icu/source/i18n/unum.cpp",
-        "third_party/icu/source/i18n/unumsys.cpp",
-        "third_party/icu/source/i18n/upluralrules.cpp",
-        "third_party/icu/source/i18n/uregex.cpp",
-        "third_party/icu/source/i18n/uregexc.cpp",
-        "third_party/icu/source/i18n/uregion.cpp",
-        "third_party/icu/source/i18n/usearch.cpp",
-        "third_party/icu/source/i18n/uspoof.cpp",
-        "third_party/icu/source/i18n/uspoof_build.cpp",
-        "third_party/icu/source/i18n/uspoof_conf.cpp",
-        "third_party/icu/source/i18n/uspoof_impl.cpp",
-        "third_party/icu/source/i18n/utf16collationiterator.cpp",
-        "third_party/icu/source/i18n/utf8collationiterator.cpp",
-        "third_party/icu/source/i18n/utmscale.cpp",
-        "third_party/icu/source/i18n/utrans.cpp",
-        "third_party/icu/source/i18n/vtzone.cpp",
-        "third_party/icu/source/i18n/vzone.cpp",
-        "third_party/icu/source/i18n/windtfmt.cpp",
-        "third_party/icu/source/i18n/winnmfmt.cpp",
-        "third_party/icu/source/i18n/wintzimpl.cpp",
-        "third_party/icu/source/i18n/zonemeta.cpp",
-        "third_party/icu/source/i18n/zrule.cpp",
-        "third_party/icu/source/i18n/ztrans.cpp",
-    ],
-    static_libs: [
-        "cronet_aml_third_party_icu_icuuc_private",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_DLOPEN=0",
-        "-DICU_UTIL_DATA_IMPL=ICU_UTIL_DATA_FILE",
-        "-DUCONFIG_ONLY_HTML_CONVERSION=1",
-        "-DUCONFIG_USE_WINDOWS_LCID_MAPPING_API=0",
-        "-DUSE_CHROMIUM_ICU=1",
-        "-DU_CHARSET_IS_UTF8=1",
-        "-DU_ENABLE_DYLOAD=0",
-        "-DU_ENABLE_RESOURCE_TRACING=0",
-        "-DU_ENABLE_TRACING=1",
-        "-DU_I18N_IMPLEMENTATION",
-        "-DU_STATIC_IMPLEMENTATION",
-        "-DU_USING_ICU_NAMESPACE=0",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/icu/source/common/",
-        "third_party/icu/source/i18n/",
-    ],
-    cpp_std: "c++20",
-    rtti: true,
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/icu:icuuc_private
-cc_library_static {
-    name: "cronet_aml_third_party_icu_icuuc_private",
-    srcs: [
-        "third_party/icu/source/common/appendable.cpp",
-        "third_party/icu/source/common/bmpset.cpp",
-        "third_party/icu/source/common/brkeng.cpp",
-        "third_party/icu/source/common/brkiter.cpp",
-        "third_party/icu/source/common/bytesinkutil.cpp",
-        "third_party/icu/source/common/bytestream.cpp",
-        "third_party/icu/source/common/bytestrie.cpp",
-        "third_party/icu/source/common/bytestriebuilder.cpp",
-        "third_party/icu/source/common/bytestrieiterator.cpp",
-        "third_party/icu/source/common/caniter.cpp",
-        "third_party/icu/source/common/characterproperties.cpp",
-        "third_party/icu/source/common/chariter.cpp",
-        "third_party/icu/source/common/charstr.cpp",
-        "third_party/icu/source/common/cmemory.cpp",
-        "third_party/icu/source/common/cstr.cpp",
-        "third_party/icu/source/common/cstring.cpp",
-        "third_party/icu/source/common/cwchar.cpp",
-        "third_party/icu/source/common/dictbe.cpp",
-        "third_party/icu/source/common/dictionarydata.cpp",
-        "third_party/icu/source/common/dtintrv.cpp",
-        "third_party/icu/source/common/edits.cpp",
-        "third_party/icu/source/common/emojiprops.cpp",
-        "third_party/icu/source/common/errorcode.cpp",
-        "third_party/icu/source/common/filteredbrk.cpp",
-        "third_party/icu/source/common/filterednormalizer2.cpp",
-        "third_party/icu/source/common/icudataver.cpp",
-        "third_party/icu/source/common/icuplug.cpp",
-        "third_party/icu/source/common/loadednormalizer2impl.cpp",
-        "third_party/icu/source/common/localebuilder.cpp",
-        "third_party/icu/source/common/localematcher.cpp",
-        "third_party/icu/source/common/localeprioritylist.cpp",
-        "third_party/icu/source/common/locavailable.cpp",
-        "third_party/icu/source/common/locbased.cpp",
-        "third_party/icu/source/common/locdispnames.cpp",
-        "third_party/icu/source/common/locdistance.cpp",
-        "third_party/icu/source/common/locdspnm.cpp",
-        "third_party/icu/source/common/locid.cpp",
-        "third_party/icu/source/common/loclikely.cpp",
-        "third_party/icu/source/common/loclikelysubtags.cpp",
-        "third_party/icu/source/common/locmap.cpp",
-        "third_party/icu/source/common/locresdata.cpp",
-        "third_party/icu/source/common/locutil.cpp",
-        "third_party/icu/source/common/lsr.cpp",
-        "third_party/icu/source/common/lstmbe.cpp",
-        "third_party/icu/source/common/messagepattern.cpp",
-        "third_party/icu/source/common/normalizer2.cpp",
-        "third_party/icu/source/common/normalizer2impl.cpp",
-        "third_party/icu/source/common/normlzr.cpp",
-        "third_party/icu/source/common/parsepos.cpp",
-        "third_party/icu/source/common/patternprops.cpp",
-        "third_party/icu/source/common/pluralmap.cpp",
-        "third_party/icu/source/common/propname.cpp",
-        "third_party/icu/source/common/propsvec.cpp",
-        "third_party/icu/source/common/punycode.cpp",
-        "third_party/icu/source/common/putil.cpp",
-        "third_party/icu/source/common/rbbi.cpp",
-        "third_party/icu/source/common/rbbi_cache.cpp",
-        "third_party/icu/source/common/rbbidata.cpp",
-        "third_party/icu/source/common/rbbinode.cpp",
-        "third_party/icu/source/common/rbbirb.cpp",
-        "third_party/icu/source/common/rbbiscan.cpp",
-        "third_party/icu/source/common/rbbisetb.cpp",
-        "third_party/icu/source/common/rbbistbl.cpp",
-        "third_party/icu/source/common/rbbitblb.cpp",
-        "third_party/icu/source/common/resbund.cpp",
-        "third_party/icu/source/common/resbund_cnv.cpp",
-        "third_party/icu/source/common/resource.cpp",
-        "third_party/icu/source/common/restrace.cpp",
-        "third_party/icu/source/common/ruleiter.cpp",
-        "third_party/icu/source/common/schriter.cpp",
-        "third_party/icu/source/common/serv.cpp",
-        "third_party/icu/source/common/servlk.cpp",
-        "third_party/icu/source/common/servlkf.cpp",
-        "third_party/icu/source/common/servls.cpp",
-        "third_party/icu/source/common/servnotf.cpp",
-        "third_party/icu/source/common/servrbf.cpp",
-        "third_party/icu/source/common/servslkf.cpp",
-        "third_party/icu/source/common/sharedobject.cpp",
-        "third_party/icu/source/common/simpleformatter.cpp",
-        "third_party/icu/source/common/static_unicode_sets.cpp",
-        "third_party/icu/source/common/stringpiece.cpp",
-        "third_party/icu/source/common/stringtriebuilder.cpp",
-        "third_party/icu/source/common/uarrsort.cpp",
-        "third_party/icu/source/common/ubidi.cpp",
-        "third_party/icu/source/common/ubidi_props.cpp",
-        "third_party/icu/source/common/ubidiln.cpp",
-        "third_party/icu/source/common/ubiditransform.cpp",
-        "third_party/icu/source/common/ubidiwrt.cpp",
-        "third_party/icu/source/common/ubrk.cpp",
-        "third_party/icu/source/common/ucase.cpp",
-        "third_party/icu/source/common/ucasemap.cpp",
-        "third_party/icu/source/common/ucasemap_titlecase_brkiter.cpp",
-        "third_party/icu/source/common/ucat.cpp",
-        "third_party/icu/source/common/uchar.cpp",
-        "third_party/icu/source/common/ucharstrie.cpp",
-        "third_party/icu/source/common/ucharstriebuilder.cpp",
-        "third_party/icu/source/common/ucharstrieiterator.cpp",
-        "third_party/icu/source/common/uchriter.cpp",
-        "third_party/icu/source/common/ucln_cmn.cpp",
-        "third_party/icu/source/common/ucmndata.cpp",
-        "third_party/icu/source/common/ucnv.cpp",
-        "third_party/icu/source/common/ucnv2022.cpp",
-        "third_party/icu/source/common/ucnv_bld.cpp",
-        "third_party/icu/source/common/ucnv_cb.cpp",
-        "third_party/icu/source/common/ucnv_cnv.cpp",
-        "third_party/icu/source/common/ucnv_ct.cpp",
-        "third_party/icu/source/common/ucnv_err.cpp",
-        "third_party/icu/source/common/ucnv_ext.cpp",
-        "third_party/icu/source/common/ucnv_io.cpp",
-        "third_party/icu/source/common/ucnv_lmb.cpp",
-        "third_party/icu/source/common/ucnv_set.cpp",
-        "third_party/icu/source/common/ucnv_u16.cpp",
-        "third_party/icu/source/common/ucnv_u32.cpp",
-        "third_party/icu/source/common/ucnv_u7.cpp",
-        "third_party/icu/source/common/ucnv_u8.cpp",
-        "third_party/icu/source/common/ucnvbocu.cpp",
-        "third_party/icu/source/common/ucnvdisp.cpp",
-        "third_party/icu/source/common/ucnvhz.cpp",
-        "third_party/icu/source/common/ucnvisci.cpp",
-        "third_party/icu/source/common/ucnvlat1.cpp",
-        "third_party/icu/source/common/ucnvmbcs.cpp",
-        "third_party/icu/source/common/ucnvscsu.cpp",
-        "third_party/icu/source/common/ucnvsel.cpp",
-        "third_party/icu/source/common/ucol_swp.cpp",
-        "third_party/icu/source/common/ucptrie.cpp",
-        "third_party/icu/source/common/ucurr.cpp",
-        "third_party/icu/source/common/udata.cpp",
-        "third_party/icu/source/common/udatamem.cpp",
-        "third_party/icu/source/common/udataswp.cpp",
-        "third_party/icu/source/common/uenum.cpp",
-        "third_party/icu/source/common/uhash.cpp",
-        "third_party/icu/source/common/uhash_us.cpp",
-        "third_party/icu/source/common/uidna.cpp",
-        "third_party/icu/source/common/uinit.cpp",
-        "third_party/icu/source/common/uinvchar.cpp",
-        "third_party/icu/source/common/uiter.cpp",
-        "third_party/icu/source/common/ulist.cpp",
-        "third_party/icu/source/common/uloc.cpp",
-        "third_party/icu/source/common/uloc_keytype.cpp",
-        "third_party/icu/source/common/uloc_tag.cpp",
-        "third_party/icu/source/common/umapfile.cpp",
-        "third_party/icu/source/common/umath.cpp",
-        "third_party/icu/source/common/umutablecptrie.cpp",
-        "third_party/icu/source/common/umutex.cpp",
-        "third_party/icu/source/common/unames.cpp",
-        "third_party/icu/source/common/unifiedcache.cpp",
-        "third_party/icu/source/common/unifilt.cpp",
-        "third_party/icu/source/common/unifunct.cpp",
-        "third_party/icu/source/common/uniset.cpp",
-        "third_party/icu/source/common/uniset_closure.cpp",
-        "third_party/icu/source/common/uniset_props.cpp",
-        "third_party/icu/source/common/unisetspan.cpp",
-        "third_party/icu/source/common/unistr.cpp",
-        "third_party/icu/source/common/unistr_case.cpp",
-        "third_party/icu/source/common/unistr_case_locale.cpp",
-        "third_party/icu/source/common/unistr_cnv.cpp",
-        "third_party/icu/source/common/unistr_props.cpp",
-        "third_party/icu/source/common/unistr_titlecase_brkiter.cpp",
-        "third_party/icu/source/common/unorm.cpp",
-        "third_party/icu/source/common/unormcmp.cpp",
-        "third_party/icu/source/common/uobject.cpp",
-        "third_party/icu/source/common/uprops.cpp",
-        "third_party/icu/source/common/ures_cnv.cpp",
-        "third_party/icu/source/common/uresbund.cpp",
-        "third_party/icu/source/common/uresdata.cpp",
-        "third_party/icu/source/common/usc_impl.cpp",
-        "third_party/icu/source/common/uscript.cpp",
-        "third_party/icu/source/common/uscript_props.cpp",
-        "third_party/icu/source/common/uset.cpp",
-        "third_party/icu/source/common/uset_props.cpp",
-        "third_party/icu/source/common/usetiter.cpp",
-        "third_party/icu/source/common/ushape.cpp",
-        "third_party/icu/source/common/usprep.cpp",
-        "third_party/icu/source/common/ustack.cpp",
-        "third_party/icu/source/common/ustr_cnv.cpp",
-        "third_party/icu/source/common/ustr_titlecase_brkiter.cpp",
-        "third_party/icu/source/common/ustr_wcs.cpp",
-        "third_party/icu/source/common/ustrcase.cpp",
-        "third_party/icu/source/common/ustrcase_locale.cpp",
-        "third_party/icu/source/common/ustrenum.cpp",
-        "third_party/icu/source/common/ustrfmt.cpp",
-        "third_party/icu/source/common/ustring.cpp",
-        "third_party/icu/source/common/ustrtrns.cpp",
-        "third_party/icu/source/common/utext.cpp",
-        "third_party/icu/source/common/utf_impl.cpp",
-        "third_party/icu/source/common/util.cpp",
-        "third_party/icu/source/common/util_props.cpp",
-        "third_party/icu/source/common/utrace.cpp",
-        "third_party/icu/source/common/utrie.cpp",
-        "third_party/icu/source/common/utrie2.cpp",
-        "third_party/icu/source/common/utrie2_builder.cpp",
-        "third_party/icu/source/common/utrie_swap.cpp",
-        "third_party/icu/source/common/uts46.cpp",
-        "third_party/icu/source/common/utypes.cpp",
-        "third_party/icu/source/common/uvector.cpp",
-        "third_party/icu/source/common/uvectr32.cpp",
-        "third_party/icu/source/common/uvectr64.cpp",
-        "third_party/icu/source/common/wintz.cpp",
-        "third_party/icu/source/stubdata/stubdata.cpp",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_DLOPEN=0",
-        "-DICU_UTIL_DATA_IMPL=ICU_UTIL_DATA_FILE",
-        "-DUCONFIG_ONLY_HTML_CONVERSION=1",
-        "-DUCONFIG_USE_WINDOWS_LCID_MAPPING_API=0",
-        "-DUSE_CHROMIUM_ICU=1",
-        "-DU_CHARSET_IS_UTF8=1",
-        "-DU_COMMON_IMPLEMENTATION",
-        "-DU_ENABLE_DYLOAD=0",
-        "-DU_ENABLE_RESOURCE_TRACING=0",
-        "-DU_ENABLE_TRACING=1",
-        "-DU_ICUDATAENTRY_IN_COMMON",
-        "-DU_STATIC_IMPLEMENTATION",
-        "-DU_USING_ICU_NAMESPACE=0",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/icu/source/common/",
-        "third_party/icu/source/i18n/",
-    ],
-    cpp_std: "c++20",
-    rtti: true,
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/icu:icuuc_public
-cc_object {
-    name: "cronet_aml_third_party_icu_icuuc_public",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/libevent:libevent
-cc_library_static {
-    name: "cronet_aml_third_party_libevent_libevent",
-    srcs: [
-        "third_party/libevent/buffer.c",
-        "third_party/libevent/epoll.c",
-        "third_party/libevent/evbuffer.c",
-        "third_party/libevent/evdns.c",
-        "third_party/libevent/event.c",
-        "third_party/libevent/event_tagging.c",
-        "third_party/libevent/evrpc.c",
-        "third_party/libevent/evutil.c",
-        "third_party/libevent/http.c",
-        "third_party/libevent/log.c",
-        "third_party/libevent/poll.c",
-        "third_party/libevent/select.c",
-        "third_party/libevent/signal.c",
-        "third_party/libevent/strlcpy.c",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_CONFIG_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            local_include_dirs: [
-                "third_party/libevent/android/",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-            local_include_dirs: [
-                "third_party/libevent/android/",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            local_include_dirs: [
-                "third_party/libevent/android/",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-            local_include_dirs: [
-                "third_party/libevent/android/",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-            local_include_dirs: [
-                "third_party/libevent/linux/",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/metrics_proto:metrics_proto
-cc_genrule {
-    name: "cronet_aml_third_party_metrics_proto_metrics_proto_gen",
-    srcs: [
-        "third_party/metrics_proto/call_stack_profile.proto",
-        "third_party/metrics_proto/cast_logs.proto",
-        "third_party/metrics_proto/chrome_os_app_list_launch_event.proto",
-        "third_party/metrics_proto/chrome_searchbox_stats.proto",
-        "third_party/metrics_proto/chrome_user_metrics_extension.proto",
-        "third_party/metrics_proto/custom_tab_session.proto",
-        "third_party/metrics_proto/execution_context.proto",
-        "third_party/metrics_proto/extension_install.proto",
-        "third_party/metrics_proto/histogram_event.proto",
-        "third_party/metrics_proto/omnibox_event.proto",
-        "third_party/metrics_proto/omnibox_focus_type.proto",
-        "third_party/metrics_proto/omnibox_input_type.proto",
-        "third_party/metrics_proto/perf_data.proto",
-        "third_party/metrics_proto/perf_stat.proto",
-        "third_party/metrics_proto/printer_event.proto",
-        "third_party/metrics_proto/reporting_info.proto",
-        "third_party/metrics_proto/sampled_profile.proto",
-        "third_party/metrics_proto/structured_data.proto",
-        "third_party/metrics_proto/system_profile.proto",
-        "third_party/metrics_proto/trace_log.proto",
-        "third_party/metrics_proto/translate_event.proto",
-        "third_party/metrics_proto/ukm/aggregate.proto",
-        "third_party/metrics_proto/ukm/entry.proto",
-        "third_party/metrics_proto/ukm/report.proto",
-        "third_party/metrics_proto/ukm/source.proto",
-        "third_party/metrics_proto/user_action_event.proto",
-        "third_party/metrics_proto/user_demographics.proto",
-    ],
-    tools: [
-        "cronet_aml_third_party_protobuf_protoc",
-    ],
-    cmd: "$(location cronet_aml_third_party_protobuf_protoc) --proto_path=external/chromium_org/third_party/metrics_proto --cpp_out=lite=true:$(genDir)/external/chromium_org/third_party/metrics_proto/ $(in)",
-    out: [
-        "external/chromium_org/third_party/metrics_proto/call_stack_profile.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/cast_logs.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/chrome_os_app_list_launch_event.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/chrome_searchbox_stats.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/chrome_user_metrics_extension.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/custom_tab_session.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/execution_context.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/extension_install.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/histogram_event.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/omnibox_event.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/omnibox_focus_type.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/omnibox_input_type.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/perf_data.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/perf_stat.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/printer_event.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/reporting_info.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/sampled_profile.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/structured_data.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/system_profile.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/trace_log.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/translate_event.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/ukm/aggregate.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/ukm/entry.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/ukm/report.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/ukm/source.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/user_action_event.pb.cc",
-        "external/chromium_org/third_party/metrics_proto/user_demographics.pb.cc",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //third_party/metrics_proto:metrics_proto
-cc_genrule {
-    name: "cronet_aml_third_party_metrics_proto_metrics_proto_gen_headers",
-    srcs: [
-        "third_party/metrics_proto/call_stack_profile.proto",
-        "third_party/metrics_proto/cast_logs.proto",
-        "third_party/metrics_proto/chrome_os_app_list_launch_event.proto",
-        "third_party/metrics_proto/chrome_searchbox_stats.proto",
-        "third_party/metrics_proto/chrome_user_metrics_extension.proto",
-        "third_party/metrics_proto/custom_tab_session.proto",
-        "third_party/metrics_proto/execution_context.proto",
-        "third_party/metrics_proto/extension_install.proto",
-        "third_party/metrics_proto/histogram_event.proto",
-        "third_party/metrics_proto/omnibox_event.proto",
-        "third_party/metrics_proto/omnibox_focus_type.proto",
-        "third_party/metrics_proto/omnibox_input_type.proto",
-        "third_party/metrics_proto/perf_data.proto",
-        "third_party/metrics_proto/perf_stat.proto",
-        "third_party/metrics_proto/printer_event.proto",
-        "third_party/metrics_proto/reporting_info.proto",
-        "third_party/metrics_proto/sampled_profile.proto",
-        "third_party/metrics_proto/structured_data.proto",
-        "third_party/metrics_proto/system_profile.proto",
-        "third_party/metrics_proto/trace_log.proto",
-        "third_party/metrics_proto/translate_event.proto",
-        "third_party/metrics_proto/ukm/aggregate.proto",
-        "third_party/metrics_proto/ukm/entry.proto",
-        "third_party/metrics_proto/ukm/report.proto",
-        "third_party/metrics_proto/ukm/source.proto",
-        "third_party/metrics_proto/user_action_event.proto",
-        "third_party/metrics_proto/user_demographics.proto",
-    ],
-    tools: [
-        "cronet_aml_third_party_protobuf_protoc",
-    ],
-    cmd: "$(location cronet_aml_third_party_protobuf_protoc) --proto_path=external/chromium_org/third_party/metrics_proto --cpp_out=lite=true:$(genDir)/external/chromium_org/third_party/metrics_proto/ $(in)",
-    out: [
-        "external/chromium_org/third_party/metrics_proto/call_stack_profile.pb.h",
-        "external/chromium_org/third_party/metrics_proto/cast_logs.pb.h",
-        "external/chromium_org/third_party/metrics_proto/chrome_os_app_list_launch_event.pb.h",
-        "external/chromium_org/third_party/metrics_proto/chrome_searchbox_stats.pb.h",
-        "external/chromium_org/third_party/metrics_proto/chrome_user_metrics_extension.pb.h",
-        "external/chromium_org/third_party/metrics_proto/custom_tab_session.pb.h",
-        "external/chromium_org/third_party/metrics_proto/execution_context.pb.h",
-        "external/chromium_org/third_party/metrics_proto/extension_install.pb.h",
-        "external/chromium_org/third_party/metrics_proto/histogram_event.pb.h",
-        "external/chromium_org/third_party/metrics_proto/omnibox_event.pb.h",
-        "external/chromium_org/third_party/metrics_proto/omnibox_focus_type.pb.h",
-        "external/chromium_org/third_party/metrics_proto/omnibox_input_type.pb.h",
-        "external/chromium_org/third_party/metrics_proto/perf_data.pb.h",
-        "external/chromium_org/third_party/metrics_proto/perf_stat.pb.h",
-        "external/chromium_org/third_party/metrics_proto/printer_event.pb.h",
-        "external/chromium_org/third_party/metrics_proto/reporting_info.pb.h",
-        "external/chromium_org/third_party/metrics_proto/sampled_profile.pb.h",
-        "external/chromium_org/third_party/metrics_proto/structured_data.pb.h",
-        "external/chromium_org/third_party/metrics_proto/system_profile.pb.h",
-        "external/chromium_org/third_party/metrics_proto/trace_log.pb.h",
-        "external/chromium_org/third_party/metrics_proto/translate_event.pb.h",
-        "external/chromium_org/third_party/metrics_proto/ukm/aggregate.pb.h",
-        "external/chromium_org/third_party/metrics_proto/ukm/entry.pb.h",
-        "external/chromium_org/third_party/metrics_proto/ukm/report.pb.h",
-        "external/chromium_org/third_party/metrics_proto/ukm/source.pb.h",
-        "external/chromium_org/third_party/metrics_proto/user_action_event.pb.h",
-        "external/chromium_org/third_party/metrics_proto/user_demographics.pb.h",
-    ],
-    export_include_dirs: [
-        ".",
-        "protos",
-        "third_party/metrics_proto",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //third_party/modp_b64:modp_b64
-cc_library_static {
-    name: "cronet_aml_third_party_modp_b64_modp_b64",
-    srcs: [
-        "third_party/modp_b64/modp_b64.cc",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/protobuf:protobuf_full
-cc_library_static {
-    name: "cronet_aml_third_party_protobuf_protobuf_full",
-    srcs: [
-        "third_party/protobuf/src/google/protobuf/any.cc",
-        "third_party/protobuf/src/google/protobuf/any.pb.cc",
-        "third_party/protobuf/src/google/protobuf/any_lite.cc",
-        "third_party/protobuf/src/google/protobuf/api.pb.cc",
-        "third_party/protobuf/src/google/protobuf/arena.cc",
-        "third_party/protobuf/src/google/protobuf/arenastring.cc",
-        "third_party/protobuf/src/google/protobuf/arenaz_sampler.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/importer.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/parser.cc",
-        "third_party/protobuf/src/google/protobuf/descriptor.cc",
-        "third_party/protobuf/src/google/protobuf/descriptor.pb.cc",
-        "third_party/protobuf/src/google/protobuf/descriptor_database.cc",
-        "third_party/protobuf/src/google/protobuf/duration.pb.cc",
-        "third_party/protobuf/src/google/protobuf/dynamic_message.cc",
-        "third_party/protobuf/src/google/protobuf/empty.pb.cc",
-        "third_party/protobuf/src/google/protobuf/extension_set.cc",
-        "third_party/protobuf/src/google/protobuf/extension_set_heavy.cc",
-        "third_party/protobuf/src/google/protobuf/field_mask.pb.cc",
-        "third_party/protobuf/src/google/protobuf/generated_enum_util.cc",
-        "third_party/protobuf/src/google/protobuf/generated_message_bases.cc",
-        "third_party/protobuf/src/google/protobuf/generated_message_reflection.cc",
-        "third_party/protobuf/src/google/protobuf/generated_message_tctable_full.cc",
-        "third_party/protobuf/src/google/protobuf/generated_message_tctable_lite.cc",
-        "third_party/protobuf/src/google/protobuf/generated_message_util.cc",
-        "third_party/protobuf/src/google/protobuf/implicit_weak_message.cc",
-        "third_party/protobuf/src/google/protobuf/inlined_string_field.cc",
-        "third_party/protobuf/src/google/protobuf/io/coded_stream.cc",
-        "third_party/protobuf/src/google/protobuf/io/gzip_stream.cc",
-        "third_party/protobuf/src/google/protobuf/io/io_win32.cc",
-        "third_party/protobuf/src/google/protobuf/io/printer.cc",
-        "third_party/protobuf/src/google/protobuf/io/strtod.cc",
-        "third_party/protobuf/src/google/protobuf/io/tokenizer.cc",
-        "third_party/protobuf/src/google/protobuf/io/zero_copy_stream.cc",
-        "third_party/protobuf/src/google/protobuf/io/zero_copy_stream_impl.cc",
-        "third_party/protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.cc",
-        "third_party/protobuf/src/google/protobuf/map.cc",
-        "third_party/protobuf/src/google/protobuf/map_field.cc",
-        "third_party/protobuf/src/google/protobuf/message.cc",
-        "third_party/protobuf/src/google/protobuf/message_lite.cc",
-        "third_party/protobuf/src/google/protobuf/parse_context.cc",
-        "third_party/protobuf/src/google/protobuf/reflection_ops.cc",
-        "third_party/protobuf/src/google/protobuf/repeated_field.cc",
-        "third_party/protobuf/src/google/protobuf/repeated_ptr_field.cc",
-        "third_party/protobuf/src/google/protobuf/service.cc",
-        "third_party/protobuf/src/google/protobuf/source_context.pb.cc",
-        "third_party/protobuf/src/google/protobuf/struct.pb.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/bytestream.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/common.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/int128.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/status.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/statusor.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/stringpiece.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/stringprintf.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/structurally_valid.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/strutil.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/substitute.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/time.cc",
-        "third_party/protobuf/src/google/protobuf/text_format.cc",
-        "third_party/protobuf/src/google/protobuf/timestamp.pb.cc",
-        "third_party/protobuf/src/google/protobuf/type.pb.cc",
-        "third_party/protobuf/src/google/protobuf/unknown_field_set.cc",
-        "third_party/protobuf/src/google/protobuf/util/delimited_message_util.cc",
-        "third_party/protobuf/src/google/protobuf/util/field_comparator.cc",
-        "third_party/protobuf/src/google/protobuf/util/field_mask_util.cc",
-        "third_party/protobuf/src/google/protobuf/util/internal/datapiece.cc",
-        "third_party/protobuf/src/google/protobuf/util/internal/default_value_objectwriter.cc",
-        "third_party/protobuf/src/google/protobuf/util/internal/error_listener.cc",
-        "third_party/protobuf/src/google/protobuf/util/internal/field_mask_utility.cc",
-        "third_party/protobuf/src/google/protobuf/util/internal/json_escaping.cc",
-        "third_party/protobuf/src/google/protobuf/util/internal/json_objectwriter.cc",
-        "third_party/protobuf/src/google/protobuf/util/internal/json_stream_parser.cc",
-        "third_party/protobuf/src/google/protobuf/util/internal/object_writer.cc",
-        "third_party/protobuf/src/google/protobuf/util/internal/proto_writer.cc",
-        "third_party/protobuf/src/google/protobuf/util/internal/protostream_objectsource.cc",
-        "third_party/protobuf/src/google/protobuf/util/internal/protostream_objectwriter.cc",
-        "third_party/protobuf/src/google/protobuf/util/internal/type_info.cc",
-        "third_party/protobuf/src/google/protobuf/util/internal/utility.cc",
-        "third_party/protobuf/src/google/protobuf/util/json_util.cc",
-        "third_party/protobuf/src/google/protobuf/util/message_differencer.cc",
-        "third_party/protobuf/src/google/protobuf/util/time_util.cc",
-        "third_party/protobuf/src/google/protobuf/util/type_resolver_util.cc",
-        "third_party/protobuf/src/google/protobuf/wire_format.cc",
-        "third_party/protobuf/src/google/protobuf/wire_format_lite.cc",
-        "third_party/protobuf/src/google/protobuf/wrappers.pb.cc",
-    ],
-    static_libs: [
-        "cronet_aml_third_party_zlib_zlib",
-    ],
-    host_supported: true,
-    device_supported: false,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_ZLIB",
-        "-DUSE_AURA=1",
-        "-DUSE_OZONE=1",
-        "-DUSE_UDEV",
-        "-D_DEBUG",
-        "-D_FILE_OFFSET_BITS=64",
-        "-D_GNU_SOURCE",
-        "-D_LARGEFILE64_SOURCE",
-        "-D_LARGEFILE_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-msse3",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/protobuf/src/",
-        "third_party/zlib/",
-    ],
-    cpp_std: "c++20",
-}
-
-// GN: //third_party/protobuf:protobuf_lite
-cc_library_static {
-    name: "cronet_aml_third_party_protobuf_protobuf_lite",
-    srcs: [
-        "third_party/protobuf/src/google/protobuf/any_lite.cc",
-        "third_party/protobuf/src/google/protobuf/arena.cc",
-        "third_party/protobuf/src/google/protobuf/arenastring.cc",
-        "third_party/protobuf/src/google/protobuf/arenaz_sampler.cc",
-        "third_party/protobuf/src/google/protobuf/extension_set.cc",
-        "third_party/protobuf/src/google/protobuf/generated_enum_util.cc",
-        "third_party/protobuf/src/google/protobuf/generated_message_tctable_lite.cc",
-        "third_party/protobuf/src/google/protobuf/generated_message_util.cc",
-        "third_party/protobuf/src/google/protobuf/implicit_weak_message.cc",
-        "third_party/protobuf/src/google/protobuf/inlined_string_field.cc",
-        "third_party/protobuf/src/google/protobuf/io/coded_stream.cc",
-        "third_party/protobuf/src/google/protobuf/io/io_win32.cc",
-        "third_party/protobuf/src/google/protobuf/io/strtod.cc",
-        "third_party/protobuf/src/google/protobuf/io/zero_copy_stream.cc",
-        "third_party/protobuf/src/google/protobuf/io/zero_copy_stream_impl.cc",
-        "third_party/protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.cc",
-        "third_party/protobuf/src/google/protobuf/map.cc",
-        "third_party/protobuf/src/google/protobuf/message_lite.cc",
-        "third_party/protobuf/src/google/protobuf/parse_context.cc",
-        "third_party/protobuf/src/google/protobuf/repeated_field.cc",
-        "third_party/protobuf/src/google/protobuf/repeated_ptr_field.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/bytestream.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/common.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/int128.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/status.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/statusor.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/stringpiece.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/stringprintf.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/structurally_valid.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/strutil.cc",
-        "third_party/protobuf/src/google/protobuf/stubs/time.cc",
-        "third_party/protobuf/src/google/protobuf/wire_format_lite.cc",
-    ],
-    shared_libs: [
-        "liblog",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DHAVE_SYS_UIO_H",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/protobuf/src/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/protobuf:protoc
-cc_binary {
-    name: "cronet_aml_third_party_protobuf_protoc",
-    srcs: [
-        ":cronet_aml_buildtools_third_party_libc___libc__",
-        ":cronet_aml_buildtools_third_party_libc__abi_libc__abi",
-        "third_party/protobuf/src/google/protobuf/compiler/main.cc",
-    ],
-    static_libs: [
-        "cronet_aml_third_party_protobuf_protobuf_full",
-        "cronet_aml_third_party_protobuf_protoc_lib",
-        "cronet_aml_third_party_zlib_zlib",
-    ],
-    host_supported: true,
-    device_supported: false,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DUSE_AURA=1",
-        "-DUSE_OZONE=1",
-        "-DUSE_UDEV",
-        "-D_DEBUG",
-        "-D_FILE_OFFSET_BITS=64",
-        "-D_GNU_SOURCE",
-        "-D_LARGEFILE64_SOURCE",
-        "-D_LARGEFILE_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-msse3",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/protobuf/src/",
-    ],
-    cpp_std: "c++20",
-}
-
-// GN: //third_party/protobuf:protoc_lib
-cc_library_static {
-    name: "cronet_aml_third_party_protobuf_protoc_lib",
-    srcs: [
-        "third_party/protobuf/src/google/protobuf/compiler/code_generator.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/command_line_interface.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_enum.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_enum_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_extension.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_file.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_generator.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_helpers.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_map_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_message.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_message_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_padding_optimizer.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_parse_function_generator.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_primitive_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_service.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_string_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_doc_comment.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_enum.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_enum_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_field_base.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_generator.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_helpers.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_map_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_message.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_message_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_primitive_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_reflection_class.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_repeated_enum_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_repeated_message_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_repeated_primitive_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_source_generator_base.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_wrapper_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_context.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_doc_comment.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_enum.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_enum_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_enum_field_lite.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_enum_lite.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_extension.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_extension_lite.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_file.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_generator.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_generator_factory.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_helpers.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_kotlin_generator.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_map_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_map_field_lite.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_message.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_message_builder.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_message_builder_lite.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_message_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_message_field_lite.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_message_lite.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_name_resolver.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_primitive_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_primitive_field_lite.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_service.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_shared_code_generator.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_string_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/java/java_string_field_lite.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/js/js_generator.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/js/well_known_types_embed.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_enum.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_enum_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_extension.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_file.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_generator.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_map_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_message.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_message_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_oneof.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_primitive_field.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/php/php_generator.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/plugin.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/plugin.pb.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/python/python_generator.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/python/python_helpers.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/python/python_pyi_generator.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/ruby/ruby_generator.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/subprocess.cc",
-        "third_party/protobuf/src/google/protobuf/compiler/zip_writer.cc",
-    ],
-    static_libs: [
-        "cronet_aml_third_party_protobuf_protobuf_full",
-        "cronet_aml_third_party_zlib_zlib",
-    ],
-    host_supported: true,
-    device_supported: false,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DGOOGLE_PROTOBUF_INTERNAL_DONATE_STEAL_INLINE=0",
-        "-DGOOGLE_PROTOBUF_NO_RTTI",
-        "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
-        "-DHAVE_PTHREAD",
-        "-DUSE_AURA=1",
-        "-DUSE_OZONE=1",
-        "-DUSE_UDEV",
-        "-D_DEBUG",
-        "-D_FILE_OFFSET_BITS=64",
-        "-D_GNU_SOURCE",
-        "-D_LARGEFILE64_SOURCE",
-        "-D_LARGEFILE_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-msse3",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/protobuf/src/",
-    ],
-    cpp_std: "c++20",
-}
-
-// GN: //third_party/zlib:zlib
-cc_library_static {
-    name: "cronet_aml_third_party_zlib_zlib",
-    srcs: [
-        ":cronet_aml_third_party_zlib_zlib_adler32_simd",
-        ":cronet_aml_third_party_zlib_zlib_inflate_chunk_simd",
-        "third_party/zlib/adler32.c",
-        "third_party/zlib/compress.c",
-        "third_party/zlib/cpu_features.c",
-        "third_party/zlib/crc32.c",
-        "third_party/zlib/deflate.c",
-        "third_party/zlib/gzclose.c",
-        "third_party/zlib/gzlib.c",
-        "third_party/zlib/gzread.c",
-        "third_party/zlib/gzwrite.c",
-        "third_party/zlib/infback.c",
-        "third_party/zlib/inffast.c",
-        "third_party/zlib/inftrees.c",
-        "third_party/zlib/trees.c",
-        "third_party/zlib/uncompr.c",
-        "third_party/zlib/zutil.c",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DZLIB_DEBUG",
-        "-DZLIB_IMPLEMENTATION",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/zlib/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            srcs: [
-                ":cronet_aml_third_party_android_ndk_cpu_features",
-                ":cronet_aml_third_party_zlib_zlib_arm_crc32",
-            ],
-            cflags: [
-                "-DADLER32_SIMD_NEON",
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DARMV8_OS_ANDROID",
-                "-DCRC32_ARMV8_CRC32",
-                "-DDEFLATE_SLIDE_HASH_NEON",
-                "-DHAVE_SYS_UIO_H",
-                "-DINFLATE_CHUNK_SIMD_NEON",
-            ],
-            local_include_dirs: [
-                "third_party/android_ndk/sources/android/cpufeatures/",
-            ],
-        },
-        android_arm64: {
-            srcs: [
-                ":cronet_aml_third_party_android_ndk_cpu_features",
-                ":cronet_aml_third_party_zlib_zlib_arm_crc32",
-            ],
-            cflags: [
-                "-DADLER32_SIMD_NEON",
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DARMV8_OS_ANDROID",
-                "-DCRC32_ARMV8_CRC32",
-                "-DDEFLATE_SLIDE_HASH_NEON",
-                "-DHAVE_SYS_UIO_H",
-                "-DINFLATE_CHUNK_READ_64LE",
-                "-DINFLATE_CHUNK_SIMD_NEON",
-            ],
-            local_include_dirs: [
-                "third_party/android_ndk/sources/android/cpufeatures/",
-            ],
-        },
-        android_x86: {
-            srcs: [
-                ":cronet_aml_third_party_android_ndk_cpu_features",
-                ":cronet_aml_third_party_zlib_zlib_crc32_simd",
-            ],
-            cflags: [
-                "-DADLER32_SIMD_SSSE3",
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DCRC32_SIMD_SSE42_PCLMUL",
-                "-DDEFLATE_SLIDE_HASH_SSE2",
-                "-DHAVE_SYS_UIO_H",
-                "-DINFLATE_CHUNK_SIMD_SSE2",
-                "-DX86_NOT_WINDOWS",
-                "-msse3",
-            ],
-            local_include_dirs: [
-                "third_party/android_ndk/sources/android/cpufeatures/",
-            ],
-        },
-        android_x86_64: {
-            srcs: [
-                ":cronet_aml_third_party_android_ndk_cpu_features",
-                ":cronet_aml_third_party_zlib_zlib_crc32_simd",
-            ],
-            cflags: [
-                "-DADLER32_SIMD_SSSE3",
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DCRC32_SIMD_SSE42_PCLMUL",
-                "-DDEFLATE_SLIDE_HASH_SSE2",
-                "-DHAVE_SYS_UIO_H",
-                "-DINFLATE_CHUNK_READ_64LE",
-                "-DINFLATE_CHUNK_SIMD_SSE2",
-                "-DX86_NOT_WINDOWS",
-                "-msse3",
-            ],
-            local_include_dirs: [
-                "third_party/android_ndk/sources/android/cpufeatures/",
-            ],
-        },
-        host: {
-            srcs: [
-                ":cronet_aml_third_party_zlib_zlib_crc32_simd",
-            ],
-            cflags: [
-                "-DADLER32_SIMD_SSSE3",
-                "-DCRC32_SIMD_SSE42_PCLMUL",
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DDEFLATE_SLIDE_HASH_SSE2",
-                "-DINFLATE_CHUNK_READ_64LE",
-                "-DINFLATE_CHUNK_SIMD_SSE2",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-DX86_NOT_WINDOWS",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/zlib:zlib_adler32_simd
-cc_object {
-    name: "cronet_aml_third_party_zlib_zlib_adler32_simd",
-    srcs: [
-        "third_party/zlib/adler32_simd.c",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DZLIB_DEBUG",
-        "-DZLIB_IMPLEMENTATION",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DADLER32_SIMD_NEON",
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DADLER32_SIMD_NEON",
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DADLER32_SIMD_SSSE3",
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-DX86_NOT_WINDOWS",
-                "-msse3",
-                "-mssse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DADLER32_SIMD_SSSE3",
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-DX86_NOT_WINDOWS",
-                "-msse3",
-                "-mssse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DADLER32_SIMD_SSSE3",
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-DX86_NOT_WINDOWS",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-                "-mssse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/zlib:zlib_arm_crc32
-cc_object {
-    name: "cronet_aml_third_party_zlib_zlib_arm_crc32",
-    srcs: [
-        "third_party/zlib/crc32_simd.c",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DARMV8_OS_ANDROID",
-        "-DCRC32_ARMV8_CRC32",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-DZLIB_DEBUG",
-        "-DZLIB_IMPLEMENTATION",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/zlib/",
-    ],
-    cpp_std: "c++20",
-}
-
-// GN: //third_party/zlib:zlib_common_headers
-cc_object {
-    name: "cronet_aml_third_party_zlib_zlib_common_headers",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/zlib:zlib_crc32_simd
-cc_object {
-    name: "cronet_aml_third_party_zlib_zlib_crc32_simd",
-    srcs: [
-        "third_party/zlib/crc32_simd.c",
-        "third_party/zlib/crc_folding.c",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCRC32_SIMD_SSE42_PCLMUL",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DZLIB_DEBUG",
-        "-DZLIB_IMPLEMENTATION",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-        "-mpclmul",
-        "-msse3",
-        "-msse4.2",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/zlib:zlib_inflate_chunk_simd
-cc_object {
-    name: "cronet_aml_third_party_zlib_zlib_inflate_chunk_simd",
-    srcs: [
-        "third_party/zlib/contrib/optimizations/inffast_chunk.c",
-        "third_party/zlib/contrib/optimizations/inflate.c",
-    ],
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DZLIB_DEBUG",
-        "-DZLIB_IMPLEMENTATION",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/zlib/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-DINFLATE_CHUNK_SIMD_NEON",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-DINFLATE_CHUNK_READ_64LE",
-                "-DINFLATE_CHUNK_SIMD_NEON",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-DINFLATE_CHUNK_SIMD_SSE2",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DHAVE_SYS_UIO_H",
-                "-DINFLATE_CHUNK_READ_64LE",
-                "-DINFLATE_CHUNK_SIMD_SSE2",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DINFLATE_CHUNK_READ_64LE",
-                "-DINFLATE_CHUNK_SIMD_SSE2",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //third_party/zlib:zlib_slide_hash_simd
-cc_object {
-    name: "cronet_aml_third_party_zlib_zlib_slide_hash_simd",
-    host_supported: true,
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DZLIB_DEBUG",
-        "-DZLIB_IMPLEMENTATION",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DDEFLATE_SLIDE_HASH_NEON",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_arm64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DDEFLATE_SLIDE_HASH_NEON",
-                "-DHAVE_SYS_UIO_H",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DDEFLATE_SLIDE_HASH_SSE2",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-DANDROID",
-                "-DANDROID_NDK_VERSION_ROLL=r23_1",
-                "-DDEFLATE_SLIDE_HASH_SSE2",
-                "-DHAVE_SYS_UIO_H",
-                "-msse3",
-            ],
-        },
-        host: {
-            cflags: [
-                "-DCR_SYSROOT_KEY=20220331T153654Z-0",
-                "-DDEFLATE_SLIDE_HASH_SSE2",
-                "-DUSE_AURA=1",
-                "-DUSE_OZONE=1",
-                "-DUSE_UDEV",
-                "-D_FILE_OFFSET_BITS=64",
-                "-D_LARGEFILE64_SOURCE",
-                "-D_LARGEFILE_SOURCE",
-                "-msse3",
-            ],
-        },
-    },
-}
-
-// GN: //url:buildflags__android_arm
-cc_genrule {
-    name: "cronet_aml_url_buildflags__android_arm",
-    cmd: "echo '--flags USE_PLATFORM_ICU_ALTERNATIVES=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//url:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "url/buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //url:buildflags__android_arm64
-cc_genrule {
-    name: "cronet_aml_url_buildflags__android_arm64",
-    cmd: "echo '--flags USE_PLATFORM_ICU_ALTERNATIVES=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//url:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "url/buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //url:buildflags__android_x86
-cc_genrule {
-    name: "cronet_aml_url_buildflags__android_x86",
-    cmd: "echo '--flags USE_PLATFORM_ICU_ALTERNATIVES=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//url:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "url/buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //url:buildflags__android_x86_64
-cc_genrule {
-    name: "cronet_aml_url_buildflags__android_x86_64",
-    cmd: "echo '--flags USE_PLATFORM_ICU_ALTERNATIVES=\"true\"' | " +
-         "$(location build/write_buildflag_header.py) --output " +
-         "$(out) " +
-         "--rulename " +
-         "//url:buildflags " +
-         "--gen-dir " +
-         ". " +
-         "--definitions " +
-         "/dev/stdin",
-    out: [
-        "url/buildflags.h",
-    ],
-    tool_files: [
-        "build/write_buildflag_header.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //url:url
-cc_library_static {
-    name: "cronet_aml_url_url",
-    srcs: [
-        "url/gurl.cc",
-        "url/origin.cc",
-        "url/scheme_host_port.cc",
-        "url/third_party/mozilla/url_parse.cc",
-        "url/url_canon.cc",
-        "url/url_canon_etc.cc",
-        "url/url_canon_filesystemurl.cc",
-        "url/url_canon_fileurl.cc",
-        "url/url_canon_host.cc",
-        "url/url_canon_internal.cc",
-        "url/url_canon_ip.cc",
-        "url/url_canon_mailtourl.cc",
-        "url/url_canon_path.cc",
-        "url/url_canon_pathurl.cc",
-        "url/url_canon_query.cc",
-        "url/url_canon_relative.cc",
-        "url/url_canon_stdstring.cc",
-        "url/url_canon_stdurl.cc",
-        "url/url_constants.cc",
-        "url/url_idna_icu_alternatives_android.cc",
-        "url/url_parse_file.cc",
-        "url/url_util.cc",
-    ],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-    ],
-    static_libs: [
-        "cronet_aml_base_allocator_partition_allocator_partition_alloc",
-        "cronet_aml_base_base",
-        "cronet_aml_base_base_static",
-        "cronet_aml_base_third_party_double_conversion_double_conversion",
-        "cronet_aml_base_third_party_dynamic_annotations_dynamic_annotations",
-        "cronet_aml_third_party_boringssl_boringssl",
-        "cronet_aml_third_party_icu_icui18n",
-        "cronet_aml_third_party_icu_icuuc_private",
-        "cronet_aml_third_party_libevent_libevent",
-        "cronet_aml_third_party_modp_b64_modp_b64",
-    ],
-    defaults: [
-        "cronet_aml_defaults",
-    ],
-    cflags: [
-        "-DANDROID",
-        "-DANDROID_NDK_VERSION_ROLL=r23_1",
-        "-DCR_CLANG_REVISION=\"llvmorg-16-init-8697-g60809cd2-1\"",
-        "-DCR_LIBCXX_REVISION=47b31179d10646029c260702650a25d24f555acc",
-        "-DDCHECK_ALWAYS_ON=1",
-        "-DDYNAMIC_ANNOTATIONS_ENABLED=1",
-        "-DHAVE_SYS_UIO_H",
-        "-DIS_URL_IMPL",
-        "-D_DEBUG",
-        "-D_GNU_SOURCE",
-        "-D_LIBCPP_AVAILABILITY_CUSTOM_VERBOSE_ABORT_PROVIDED=1",
-        "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D_LIBCPP_ENABLE_ASSERTIONS_DEFAULT=1",
-        "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
-        "-D__STDC_CONSTANT_MACROS",
-        "-D__STDC_FORMAT_MACROS",
-    ],
-    local_include_dirs: [
-        "./",
-        "buildtools/third_party/libc++/",
-        "buildtools/third_party/libc++/trunk/include",
-        "buildtools/third_party/libc++abi/trunk/include",
-        "third_party/abseil-cpp/",
-        "third_party/boringssl/src/include/",
-    ],
-    cpp_std: "c++20",
-    target: {
-        android_arm: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_url_buildflags__android_arm",
-                "cronet_aml_url_url_jni_headers__android_arm",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm",
-                "cronet_aml_base_logging_buildflags__android_arm",
-                "cronet_aml_build_chromeos_buildflags__android_arm",
-                "cronet_aml_url_buildflags__android_arm",
-                "cronet_aml_url_url_jni_headers__android_arm",
-            ],
-        },
-        android_arm64: {
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_url_buildflags__android_arm64",
-                "cronet_aml_url_url_jni_headers__android_arm64",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_arm64",
-                "cronet_aml_base_logging_buildflags__android_arm64",
-                "cronet_aml_build_chromeos_buildflags__android_arm64",
-                "cronet_aml_url_buildflags__android_arm64",
-                "cronet_aml_url_url_jni_headers__android_arm64",
-            ],
-        },
-        android_x86: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_url_buildflags__android_x86",
-                "cronet_aml_url_url_jni_headers__android_x86",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86",
-                "cronet_aml_base_logging_buildflags__android_x86",
-                "cronet_aml_build_chromeos_buildflags__android_x86",
-                "cronet_aml_url_buildflags__android_x86",
-                "cronet_aml_url_url_jni_headers__android_x86",
-            ],
-        },
-        android_x86_64: {
-            cflags: [
-                "-msse3",
-            ],
-            generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_url_buildflags__android_x86_64",
-                "cronet_aml_url_url_jni_headers__android_x86_64",
-            ],
-            export_generated_headers: [
-                "cronet_aml_base_debugging_buildflags__android_x86_64",
-                "cronet_aml_base_logging_buildflags__android_x86_64",
-                "cronet_aml_build_chromeos_buildflags__android_x86_64",
-                "cronet_aml_url_buildflags__android_x86_64",
-                "cronet_aml_url_url_jni_headers__android_x86_64",
-            ],
-        },
-    },
-}
-
-// GN: //url:url_jni_headers__android_arm
-cc_genrule {
-    name: "cronet_aml_url_url_jni_headers__android_arm",
-    srcs: [
-        "url/android/java/src/org/chromium/url/IDNStringUtil.java",
-        "url/android/java/src/org/chromium/url/Origin.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/url/url_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "IDNStringUtil_jni.h " +
-         "--output_name " +
-         "Origin_jni.h " +
-         "--input_file " +
-         "$(location url/android/java/src/org/chromium/url/IDNStringUtil.java) " +
-         "--input_file " +
-         "$(location url/android/java/src/org/chromium/url/Origin.java)",
-    out: [
-        "url/url_jni_headers/IDNStringUtil_jni.h",
-        "url/url_jni_headers/Origin_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //url:url_jni_headers__android_arm64
-cc_genrule {
-    name: "cronet_aml_url_url_jni_headers__android_arm64",
-    srcs: [
-        "url/android/java/src/org/chromium/url/IDNStringUtil.java",
-        "url/android/java/src/org/chromium/url/Origin.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/url/url_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "IDNStringUtil_jni.h " +
-         "--output_name " +
-         "Origin_jni.h " +
-         "--input_file " +
-         "$(location url/android/java/src/org/chromium/url/IDNStringUtil.java) " +
-         "--input_file " +
-         "$(location url/android/java/src/org/chromium/url/Origin.java)",
-    out: [
-        "url/url_jni_headers/IDNStringUtil_jni.h",
-        "url/url_jni_headers/Origin_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //url:url_jni_headers__android_x86
-cc_genrule {
-    name: "cronet_aml_url_url_jni_headers__android_x86",
-    srcs: [
-        "url/android/java/src/org/chromium/url/IDNStringUtil.java",
-        "url/android/java/src/org/chromium/url/Origin.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/url/url_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "IDNStringUtil_jni.h " +
-         "--output_name " +
-         "Origin_jni.h " +
-         "--input_file " +
-         "$(location url/android/java/src/org/chromium/url/IDNStringUtil.java) " +
-         "--input_file " +
-         "$(location url/android/java/src/org/chromium/url/Origin.java)",
-    out: [
-        "url/url_jni_headers/IDNStringUtil_jni.h",
-        "url/url_jni_headers/Origin_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
-// GN: //url:url_jni_headers__android_x86_64
-cc_genrule {
-    name: "cronet_aml_url_url_jni_headers__android_x86_64",
-    srcs: [
-        "url/android/java/src/org/chromium/url/IDNStringUtil.java",
-        "url/android/java/src/org/chromium/url/Origin.java",
-    ],
-    cmd: "$(location base/android/jni_generator/jni_generator.py) --ptr_type " +
-         "long " +
-         "--output_dir " +
-         "$(genDir)/url/url_jni_headers " +
-         "--includes " +
-         "base/android/jni_generator/jni_generator_helper.h " +
-         "--use_proxy_hash " +
-         "--output_name " +
-         "IDNStringUtil_jni.h " +
-         "--output_name " +
-         "Origin_jni.h " +
-         "--input_file " +
-         "$(location url/android/java/src/org/chromium/url/IDNStringUtil.java) " +
-         "--input_file " +
-         "$(location url/android/java/src/org/chromium/url/Origin.java)",
-    out: [
-        "url/url_jni_headers/IDNStringUtil_jni.h",
-        "url/url_jni_headers/Origin_jni.h",
-    ],
-    tool_files: [
-        "base/android/jni_generator/android_jar.classes",
-        "base/android/jni_generator/jni_generator.py",
-        "build/android/gyp/util/__init__.py",
-        "build/android/gyp/util/build_utils.py",
-        "build/gn_helpers.py",
-    ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-}
-
diff --git a/tools/gn2bp/desc_arm.json b/tools/gn2bp/desc_arm.json
deleted file mode 100644
index 996b5f5..0000000
--- a/tools/gn2bp/desc_arm.json
+++ /dev/null
Binary files differ
diff --git a/tools/gn2bp/desc_arm64.json b/tools/gn2bp/desc_arm64.json
deleted file mode 100644
index dd8e800..0000000
--- a/tools/gn2bp/desc_arm64.json
+++ /dev/null
Binary files differ
diff --git a/tools/gn2bp/desc_x64.json b/tools/gn2bp/desc_x64.json
deleted file mode 100644
index 555f044..0000000
--- a/tools/gn2bp/desc_x64.json
+++ /dev/null
Binary files differ
diff --git a/tools/gn2bp/desc_x86.json b/tools/gn2bp/desc_x86.json
deleted file mode 100644
index 11e4e95..0000000
--- a/tools/gn2bp/desc_x86.json
+++ /dev/null
Binary files differ
diff --git a/tools/gn2bp/gen_android_bp b/tools/gn2bp/gen_android_bp
deleted file mode 100755
index c57524d..0000000
--- a/tools/gn2bp/gen_android_bp
+++ /dev/null
@@ -1,1532 +0,0 @@
-#!/usr/bin/env python3
-# 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.
-
-# This tool translates a collection of BUILD.gn files into a mostly equivalent
-# Android.bp file for the Android Soong build system. The input to the tool is a
-# JSON description of the GN build definition generated with the following
-# command:
-#
-#   gn desc out --format=json --all-toolchains "//*" > desc.json
-#
-# The tool is then given a list of GN labels for which to generate Android.bp
-# build rules. The dependencies for the GN labels are squashed to the generated
-# Android.bp target, except for actions which get their own genrule. Some
-# libraries are also mapped to their Android equivalents -- see |builtin_deps|.
-
-import argparse
-import collections
-import json
-import logging as log
-import operator
-import os
-import re
-import sys
-import copy
-from pathlib import Path
-
-import gn_utils
-
-ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-
-# Default targets to translate to the blueprint file.
-default_targets = [
-    '//components/cronet/android:cronet',
-    '//components/cronet:cronet_package',
-]
-
-# Defines a custom init_rc argument to be applied to the corresponding output
-# blueprint target.
-target_initrc = {
-    # TODO: this can probably be removed.
-}
-
-target_host_supported = [
-    # TODO: remove if this is not useful for the cronet build.
-]
-
-# Proto target groups which will be made public.
-proto_groups = {
-    # TODO: remove if this is not used for the cronet build.
-}
-
-# All module names are prefixed with this string to avoid collisions.
-module_prefix = 'cronet_aml_'
-
-# Shared libraries which are directly translated to Android system equivalents.
-shared_library_allowlist = [
-    'android',
-    'android.hardware.atrace@1.0',
-    'android.hardware.health@2.0',
-    'android.hardware.health-V1-ndk',
-    'android.hardware.power.stats@1.0',
-    "android.hardware.power.stats-V1-cpp",
-    'base',
-    'binder',
-    'binder_ndk',
-    'cutils',
-    'hidlbase',
-    'hidltransport',
-    'hwbinder',
-    'incident',
-    'log',
-    'services',
-    'statssocket',
-    "tracingproxy",
-    'utils',
-]
-
-# Static libraries which are directly translated to Android system equivalents.
-static_library_allowlist = [
-    'statslog_perfetto',
-]
-
-# Include directories that will be removed from all targets.
-local_include_dirs_denylist = [
-]
-
-# Name of the module which settings such as compiler flags for all other
-# modules.
-defaults_module = module_prefix + 'defaults'
-
-# Location of the project in the Android source tree.
-tree_path = 'external/chromium_org'
-
-# Path for the protobuf sources in the standalone build.
-buildtools_protobuf_src = '//buildtools/protobuf/src'
-
-# Location of the protobuf src dir in the Android source tree.
-android_protobuf_src = 'external/protobuf/src'
-
-# put all args on a new line for better diffs.
-NEWLINE = ' " +\n         "'
-
-# Compiler flags which are passed through to the blueprint.
-cflag_allowlist = [
-  # needed for zlib:zlib
-  "-mpclmul",
-  # needed for zlib:zlib
-  "-mssse3",
-  # needed for zlib:zlib
-  "-msse3",
-  # needed for zlib:zlib
-  "-msse4.2",
-]
-
-# Additional arguments to apply to Android.bp rules.
-additional_args = {
-    # TODO: remove if not needed.
-    'cronet_aml_components_cronet_android_cronet': [
-        ('linker_scripts', {
-            'base/android/library_loader/anchor_functions.lds',
-        }),
-    ],
-    'cronet_aml_net_net': [
-        ('export_static_lib_headers', {
-            'cronet_aml_net_third_party_quiche_quiche',
-            'cronet_aml_crypto_crypto',
-        }),
-    ],
-}
-
-# Android equivalents for third-party libraries that the upstream project
-# depends on.
-builtin_deps = {
-    '//net/tools/root_store_tool:root_store_tool':
-        lambda x: None,
-}
-
-# Name of tethering apex module
-tethering_apex = "com.android.tethering"
-
-# ----------------------------------------------------------------------------
-# End of configuration.
-# ----------------------------------------------------------------------------
-
-
-class Error(Exception):
-  pass
-
-
-class ThrowingArgumentParser(argparse.ArgumentParser):
-
-  def __init__(self, context):
-    super(ThrowingArgumentParser, self).__init__()
-    self.context = context
-
-  def error(self, message):
-    raise Error('%s: %s' % (self.context, message))
-
-
-def write_blueprint_key_value(output, name, value, sort=True):
-  """Writes a Blueprint key-value pair to the output"""
-
-  if isinstance(value, bool):
-    if value:
-      output.append('    %s: true,' % name)
-    else:
-      output.append('    %s: false,' % name)
-    return
-  if not value:
-    return
-  if isinstance(value, set):
-    value = sorted(value)
-  if isinstance(value, list):
-    output.append('    %s: [' % name)
-    for item in sorted(value) if sort else value:
-      output.append('        "%s",' % item)
-    output.append('    ],')
-    return
-  if isinstance(value, Target):
-    value.to_string(output)
-    return
-  if isinstance(value, dict):
-    kv_output = []
-    for k, v in value.items():
-      write_blueprint_key_value(kv_output, k, v)
-
-    output.append('    %s: {' % name)
-    for line in kv_output:
-      output.append('    %s' % line)
-    output.append('    },')
-    return
-  output.append('    %s: "%s",' % (name, value))
-
-
-class Target(object):
-  """A target-scoped part of a module"""
-
-  def __init__(self, name):
-    self.name = name
-    self.srcs = set()
-    self.shared_libs = set()
-    self.static_libs = set()
-    self.whole_static_libs = set()
-    self.header_libs = set()
-    self.cflags = set()
-    self.dist = dict()
-    self.strip = dict()
-    self.stl = None
-    self.cppflags = set()
-    self.local_include_dirs = set()
-    self.export_system_include_dirs = set()
-    self.generated_headers = set()
-    self.export_generated_headers = set()
-
-  def to_string(self, output):
-    nested_out = []
-    self._output_field(nested_out, 'srcs')
-    self._output_field(nested_out, 'shared_libs')
-    self._output_field(nested_out, 'static_libs')
-    self._output_field(nested_out, 'whole_static_libs')
-    self._output_field(nested_out, 'header_libs')
-    self._output_field(nested_out, 'cflags')
-    self._output_field(nested_out, 'stl')
-    self._output_field(nested_out, 'dist')
-    self._output_field(nested_out, 'strip')
-    self._output_field(nested_out, 'cppflags')
-    self._output_field(nested_out, 'local_include_dirs')
-    self._output_field(nested_out, 'export_system_include_dirs')
-    self._output_field(nested_out, 'generated_headers')
-    self._output_field(nested_out, 'export_generated_headers')
-
-    if nested_out:
-      output.append('    %s: {' % self.name)
-      for line in nested_out:
-        output.append('    %s' % line)
-      output.append('    },')
-
-  def _output_field(self, output, name, sort=True):
-    value = getattr(self, name)
-    return write_blueprint_key_value(output, name, value, sort)
-
-
-class Module(object):
-  """A single module (e.g., cc_binary, cc_test) in a blueprint."""
-
-  def __init__(self, mod_type, name, gn_target):
-    self.type = mod_type
-    self.gn_target = gn_target
-    self.name = name
-    self.srcs = set()
-    self.comment = 'GN: ' + gn_target
-    self.shared_libs = set()
-    self.static_libs = set()
-    self.whole_static_libs = set()
-    self.runtime_libs = set()
-    self.tools = set()
-    self.cmd = None
-    self.host_supported = False
-    self.device_supported = True
-    self.vendor_available = False
-    self.init_rc = set()
-    self.out = set()
-    self.export_include_dirs = set()
-    self.generated_headers = set()
-    self.export_generated_headers = set()
-    self.export_static_lib_headers = set()
-    self.defaults = set()
-    self.cflags = set()
-    self.include_dirs = set()
-    self.local_include_dirs = set()
-    self.header_libs = set()
-    self.required = set()
-    self.tool_files = set()
-    # target contains a dict of Targets indexed by os_arch.
-    # example: { 'android_x86': Target('android_x86')
-    self.target = dict()
-    self.target['android'] = Target('android')
-    self.target['android_x86'] = Target('android_x86')
-    self.target['android_x86_64'] = Target('android_x86_64')
-    self.target['android_arm'] = Target('android_arm')
-    self.target['android_arm64'] = Target('android_arm64')
-    self.target['host'] = Target('host')
-    self.stl = None
-    self.cpp_std = None
-    self.dist = dict()
-    self.strip = dict()
-    self.data = set()
-    self.apex_available = set()
-    self.min_sdk_version = None
-    self.proto = dict()
-    self.linker_scripts = set()
-    # The genrule_XXX below are properties that must to be propagated back
-    # on the module(s) that depend on the genrule.
-    self.genrule_headers = set()
-    self.genrule_srcs = set()
-    self.genrule_shared_libs = set()
-    self.genrule_header_libs = set()
-    self.version_script = None
-    self.test_suites = set()
-    self.test_config = None
-    self.stubs = {}
-    self.cppflags = set()
-    self.rtti = False
-    # Name of the output. Used for setting .so file name for libcronet
-    self.libs = set()
-    self.stem = None
-    self.compile_multilib = None
-    self.aidl = dict()
-    self.plugins = set()
-    self.processor_class = None
-    self.sdk_version = None
-
-  def to_string(self, output):
-    if self.comment:
-      output.append('// %s' % self.comment)
-    output.append('%s {' % self.type)
-    self._output_field(output, 'name')
-    self._output_field(output, 'srcs')
-    self._output_field(output, 'shared_libs')
-    self._output_field(output, 'static_libs')
-    self._output_field(output, 'whole_static_libs')
-    self._output_field(output, 'runtime_libs')
-    self._output_field(output, 'tools')
-    self._output_field(output, 'cmd', sort=False)
-    if self.host_supported:
-      self._output_field(output, 'host_supported')
-    if not self.device_supported:
-      self._output_field(output, 'device_supported')
-    if self.vendor_available:
-      self._output_field(output, 'vendor_available')
-    self._output_field(output, 'init_rc')
-    self._output_field(output, 'out')
-    self._output_field(output, 'export_include_dirs')
-    self._output_field(output, 'generated_headers')
-    self._output_field(output, 'export_generated_headers')
-    self._output_field(output, 'export_static_lib_headers')
-    self._output_field(output, 'defaults')
-    self._output_field(output, 'cflags')
-    self._output_field(output, 'include_dirs')
-    self._output_field(output, 'local_include_dirs')
-    self._output_field(output, 'header_libs')
-    self._output_field(output, 'required')
-    self._output_field(output, 'dist')
-    self._output_field(output, 'strip')
-    self._output_field(output, 'tool_files')
-    self._output_field(output, 'data')
-    self._output_field(output, 'stl')
-    self._output_field(output, 'cpp_std')
-    self._output_field(output, 'apex_available')
-    self._output_field(output, 'min_sdk_version')
-    self._output_field(output, 'version_script')
-    self._output_field(output, 'test_suites')
-    self._output_field(output, 'test_config')
-    self._output_field(output, 'stubs')
-    self._output_field(output, 'proto')
-    self._output_field(output, 'linker_scripts')
-    self._output_field(output, 'cppflags')
-    self._output_field(output, 'libs')
-    self._output_field(output, 'stem')
-    self._output_field(output, 'compile_multilib')
-    self._output_field(output, 'aidl')
-    self._output_field(output, 'plugins')
-    self._output_field(output, 'processor_class')
-    self._output_field(output, 'sdk_version')
-    if self.rtti:
-      self._output_field(output, 'rtti')
-
-    target_out = []
-    for arch, target in sorted(self.target.items()):
-      # _output_field calls getattr(self, arch).
-      setattr(self, arch, target)
-      self._output_field(target_out, arch)
-
-    if target_out:
-      output.append('    target: {')
-      for line in target_out:
-        output.append('    %s' % line)
-      output.append('    },')
-
-    output.append('}')
-    output.append('')
-
-  def add_android_static_lib(self, lib):
-    if self.type == 'cc_binary_host':
-      raise Exception('Adding Android static lib for host tool is unsupported')
-    elif self.host_supported:
-      self.target['android'].static_libs.add(lib)
-    else:
-      self.static_libs.add(lib)
-
-  def add_android_shared_lib(self, lib):
-    if self.type == 'cc_binary_host':
-      raise Exception('Adding Android shared lib for host tool is unsupported')
-    elif self.host_supported:
-      self.target['android'].shared_libs.add(lib)
-    else:
-      self.shared_libs.add(lib)
-
-  def _output_field(self, output, name, sort=True):
-    value = getattr(self, name)
-    return write_blueprint_key_value(output, name, value, sort)
-
-  def is_compiled(self):
-    return self.type not in ('cc_genrule', 'filegroup', 'java_genrule')
-
-  def is_genrule(self):
-    return self.type == "cc_genrule"
-
-  def has_input_files(self):
-    return len(self.srcs) > 0 or any([len(target.srcs) > 0 for target in self.target.values()])
-
-  def merge_attribute(self, key, source_module, allowed_archs, source_key = None):
-    """
-    Merges the value of the attribute `source_key` for the `dep_module` with
-    the value of the attribute `key` for this module. If the value of the
-    `source_key` is equal to None. Then `key` is used for both modules.
-
-    This merges the attribute for both non-arch and archs
-    specified in `allowed_archs`.
-    :param key: The attribute used for merging in the calling module. Also
-    used for `dep_module` if the `source_key` is None.
-    :param source_module: The module where data is propagated from.
-    :param allowed_archs: A list of archs to merge the attribute on.
-    :param source_key: if the attribute merged from the `dep_module`
-    is different from the `key`
-    """
-    if not source_key:
-      source_key = key
-    self.__dict__[key].update(source_module.__dict__[source_key])
-    for arch_name in source_module.target.keys():
-      if arch_name in allowed_archs:
-        self.target[arch_name].__dict__[key].update(
-            source_module.target[arch_name].__dict__[source_key])
-
-class Blueprint(object):
-  """In-memory representation of an Android.bp file."""
-
-  def __init__(self):
-    self.modules = {}
-
-  def add_module(self, module):
-    """Adds a new module to the blueprint, replacing any existing module
-        with the same name.
-
-        Args:
-            module: Module instance.
-        """
-    self.modules[module.name] = module
-
-  def to_string(self, output):
-    for m in sorted(self.modules.values(), key=lambda m: m.name):
-      m.to_string(output)
-
-
-def label_to_module_name(label):
-  """Turn a GN label (e.g., //:perfetto_tests) into a module name."""
-  module = re.sub(r'^//:?', '', label)
-  module = re.sub(r'[^a-zA-Z0-9_]', '_', module)
-
-  if not module.startswith(module_prefix):
-    return module_prefix + module
-  return module
-
-
-def is_supported_source_file(name):
-  """Returns True if |name| can appear in a 'srcs' list."""
-  return os.path.splitext(name)[1] in ['.c', '.cc', '.cpp', '.java', '.proto', '.S']
-
-
-def create_proto_modules(blueprint, gn, target):
-  """Generate genrules for a proto GN target.
-
-    GN actions are used to dynamically generate files during the build. The
-    Soong equivalent is a genrule. This function turns a specific kind of
-    genrule which turns .proto files into source and header files into a pair
-    equivalent genrules.
-
-    Args:
-        blueprint: Blueprint instance which is being generated.
-        target: gn_utils.Target object.
-
-    Returns:
-        The source_genrule module.
-    """
-  assert (target.type == 'proto_library')
-
-  protoc_gn_target_name = gn.get_target('//third_party/protobuf:protoc').name
-  protoc_module_name = label_to_module_name(protoc_gn_target_name)
-  tools = {protoc_module_name}
-  cpp_out_dir = '$(genDir)/%s/%s/' % (tree_path, target.proto_in_dir)
-  target_module_name = label_to_module_name(target.name)
-
-  # In GN builds the proto path is always relative to the output directory
-  # (out/tmp.xxx).
-  cmd = ['$(location %s)' % protoc_module_name]
-  cmd += ['--proto_path=%s/%s' % (tree_path, target.proto_in_dir)]
-
-  if buildtools_protobuf_src in target.proto_paths:
-    cmd += ['--proto_path=%s' % android_protobuf_src]
-
-  # We don't generate any targets for source_set proto modules because
-  # they will be inlined into other modules if required.
-  if target.proto_plugin == 'source_set':
-    return None
-
-  # Descriptor targets only generate a single target.
-  if target.proto_plugin == 'descriptor':
-    out = '{}.bin'.format(target_module_name)
-
-    cmd += ['--descriptor_set_out=$(out)']
-    cmd += ['$(in)']
-
-    descriptor_module = Module('cc_genrule', target_module_name, target.name)
-    descriptor_module.cmd = ' '.join(cmd)
-    descriptor_module.out = [out]
-    descriptor_module.tools = tools
-    blueprint.add_module(descriptor_module)
-
-    # Recursively extract the .proto files of all the dependencies and
-    # add them to srcs.
-    descriptor_module.srcs.update(
-        gn_utils.label_to_path(src) for src in target.sources)
-    for dep in target.transitive_proto_deps:
-      current_target = gn.get_target(dep)
-      descriptor_module.srcs.update(
-          gn_utils.label_to_path(src) for src in current_target.sources)
-
-    return descriptor_module
-
-  # We create two genrules for each proto target: one for the headers and
-  # another for the sources. This is because the module that depends on the
-  # generated files needs to declare two different types of dependencies --
-  # source files in 'srcs' and headers in 'generated_headers' -- and it's not
-  # valid to generate .h files from a source dependency and vice versa.
-  source_module_name = target_module_name + '_gen'
-  source_module = Module('cc_genrule', source_module_name, target.name)
-  blueprint.add_module(source_module)
-  source_module.srcs.update(
-      gn_utils.label_to_path(src) for src in target.sources)
-
-  header_module = Module('cc_genrule', source_module_name + '_headers',
-                         target.name)
-  blueprint.add_module(header_module)
-  header_module.srcs = set(source_module.srcs)
-
-  # TODO(primiano): at some point we should remove this. This was introduced
-  # by aosp/1108421 when adding "protos/" to .proto include paths, in order to
-  # avoid doing multi-repo changes and allow old clients in the android tree
-  # to still do the old #include "perfetto/..." rather than
-  # #include "protos/perfetto/...".
-  header_module.export_include_dirs = {'.', 'protos'}
-  # Since the .cc file and .h get created by a different gerule target, they
-  # are not put in the same intermediate path, so local includes do not work
-  # without explictily exporting the include dir.
-  header_module.export_include_dirs.add(target.proto_in_dir)
-
-  # This function does not return header_module so setting apex_available attribute here.
-  header_module.apex_available.add(tethering_apex)
-
-  source_module.genrule_srcs.add(':' + source_module.name)
-  source_module.genrule_headers.add(header_module.name)
-
-  if target.proto_plugin == 'proto':
-    suffixes = ['pb']
-    source_module.genrule_shared_libs.add('libprotobuf-cpp-lite')
-    cmd += ['--cpp_out=lite=true:' + cpp_out_dir]
-  elif target.proto_plugin == 'protozero':
-    suffixes = ['pbzero']
-    plugin = create_modules_from_target(blueprint, gn, protozero_plugin)
-    tools.add(plugin.name)
-    cmd += ['--plugin=protoc-gen-plugin=$(location %s)' % plugin.name]
-    cmd += ['--plugin_out=wrapper_namespace=pbzero:' + cpp_out_dir]
-  elif target.proto_plugin == 'cppgen':
-    suffixes = ['gen']
-    plugin = create_modules_from_target(blueprint, gn, cppgen_plugin)
-    tools.add(plugin.name)
-    cmd += ['--plugin=protoc-gen-plugin=$(location %s)' % plugin.name]
-    cmd += ['--plugin_out=wrapper_namespace=gen:' + cpp_out_dir]
-  elif target.proto_plugin == 'ipc':
-    suffixes = ['ipc']
-    plugin = create_modules_from_target(blueprint, gn, ipc_plugin)
-    tools.add(plugin.name)
-    cmd += ['--plugin=protoc-gen-plugin=$(location %s)' % plugin.name]
-    cmd += ['--plugin_out=wrapper_namespace=gen:' + cpp_out_dir]
-  else:
-    raise Error('Unsupported proto plugin: %s' % target.proto_plugin)
-
-  cmd += ['$(in)']
-  source_module.cmd = ' '.join(cmd)
-  header_module.cmd = source_module.cmd
-  source_module.tools = tools
-  header_module.tools = tools
-
-  for sfx in suffixes:
-    source_module.out.update('%s/%s' %
-                             (tree_path, src.replace('.proto', '.%s.cc' % sfx))
-                             for src in source_module.srcs)
-    header_module.out.update('%s/%s' %
-                             (tree_path, src.replace('.proto', '.%s.h' % sfx))
-                             for src in header_module.srcs)
-  return source_module
-
-
-def create_proto_group_modules(blueprint, gn, module_name, target_names):
-  # TODO(lalitm): today, we're only adding a Java lite module because that's
-  # the only one used in practice. In the future, if we need other target types
-  # (e.g. C++, Java full etc.) add them here.
-  bp_module_name = label_to_module_name(module_name) + '_java_protos'
-  module = Module('java_library', bp_module_name, bp_module_name)
-  module.comment = f'''GN: [{', '.join(target_names)}]'''
-  module.proto = {'type': 'lite', 'canonical_path_from_root': False}
-
-  for name in target_names:
-    target = gn.get_target(name)
-    module.srcs.update(gn_utils.label_to_path(src) for src in target.sources)
-    for dep_label in target.transitive_proto_deps:
-      dep = gn.get_target(dep_label)
-      module.srcs.update(gn_utils.label_to_path(src) for src in dep.sources)
-
-  blueprint.add_module(module)
-
-def create_gcc_preprocess_modules(blueprint, target):
-  # gcc_preprocess.py internally execute host gcc which is not allowed in genrule.
-  # So, this function create multiple modules and realize equivalent processing
-  # TODO: Consider to support gcc_preprocess.py in different way
-  # It's not great to have genrule and cc_object in the dependency from java_library
-  assert (len(target.sources) == 1)
-  source = list(target.sources)[0]
-  assert (Path(source).suffix == '.template')
-  stem = Path(source).stem
-
-  bp_module_name = label_to_module_name(target.name)
-
-  # Rename .template to .cc since cc_object does not accept .template file as srcs
-  rename_module = Module('genrule', bp_module_name + '_rename', target.name)
-  rename_module.srcs.add(gn_utils.label_to_path(source))
-  rename_module.out.add(stem + '.cc')
-  rename_module.cmd = 'cp $(in) $(out)'
-  blueprint.add_module(rename_module)
-
-  # Preprocess template file and generates java file
-  preprocess_module = Module('cc_object', bp_module_name + '_preprocess', target.name)
-  # -E: stop after preprocessing.
-  # -P: disable line markers, i.e. '#line 309'
-  preprocess_module.cflags.update(['-E', '-P', '-DANDROID'])
-  preprocess_module.srcs.add(':' + rename_module.name)
-  defines = ['-D' + target.args[i+1] for i, arg in enumerate(target.args) if arg == '--define']
-  preprocess_module.cflags.update(defines)
-  # HACK: Specifying compile_multilib to build cc_object only once.
-  # Without this, soong complain to genrule that depends on cc_object when built for 64bit target.
-  # It seems this is because cc object is a module with per-architecture variants and genrule is a
-  # module with default variant. For 64bit target, cc_object is built multiple times for 32/64bit
-  # modes and genrule doesn't know which one to depend on.
-  preprocess_module.compile_multilib = 'first'
-  blueprint.add_module(preprocess_module)
-
-  # Generates srcjar using soong_zip
-  module = Module('genrule', bp_module_name, target.name)
-  module.srcs.add(':' + preprocess_module.name)
-  module.out.add(stem + '.srcjar')
-  module.cmd = NEWLINE.join([
-    f'cp $(in) $(genDir)/{stem}.java &&',
-    f'$(location soong_zip) -o $(out) -srcjar -f $(genDir)/{stem}.java'
-  ])
-  module.tools.add('soong_zip')
-  blueprint.add_module(module)
-  return module
-
-
-class BaseActionSanitizer():
-  def __init__(self, target):
-    # Just to be on the safe side, create a deep-copy.
-    self.target = copy.deepcopy(target)
-    self.target.args = self._normalize_args()
-
-  def get_name(self):
-    return label_to_module_name(self.target.name)
-
-  def _normalize_args(self):
-    # Convert ['--param=value'] to ['--param', 'value'] for consistency.
-    # Escape quotations.
-    normalized_args = []
-    for arg in self.target.args:
-      arg = arg.replace('"', r'\"')
-      if arg.startswith('-'):
-        normalized_args.extend(arg.split('='))
-      else:
-        normalized_args.append(arg)
-    return normalized_args
-
-  # There are three types of args:
-  # - flags (--flag)
-  # - value args (--arg value)
-  # - list args (--arg value1 --arg value2)
-  # value args have exactly one arg value pair and list args have one or more arg value pairs.
-  # Note that the set of list args contains the set of value args.
-  # This is because list and value args are identical when the list args has only one arg value pair
-  # Some functions provide special implementations for each type, while others
-  # work on all of them.
-  def _has_arg(self, arg):
-    return arg in self.target.args
-
-  def _get_arg_indices(self, target_arg):
-    return [i for i, arg in enumerate(self.target.args) if arg == target_arg]
-
-  # Whether an arg value pair appears once or more times
-  def _is_list_arg(self, arg):
-    indices = self._get_arg_indices(arg)
-    return len(indices) > 0 and all([not self.target.args[i + 1].startswith('--') for i in indices])
-
-  def _update_list_arg(self, arg, func, throw_if_absent = True):
-    if self._should_fail_silently(arg, throw_if_absent):
-      return
-    assert(self._is_list_arg(arg))
-    indices = self._get_arg_indices(arg)
-    for i in indices:
-      self._set_arg_at(i + 1, func(self.target.args[i + 1]))
-
-  # Whether an arg value pair appears exactly once
-  def _is_value_arg(self, arg):
-    return operator.countOf(self.target.args, arg) == 1 and self._is_list_arg(arg)
-
-  def _get_value_arg(self, arg):
-    assert(self._is_value_arg(arg))
-    i = self.target.args.index(arg)
-    return self.target.args[i + 1]
-
-  # used to check whether a function call should cause an error when an arg is
-  # missing.
-  def _should_fail_silently(self, arg, throw_if_absent):
-    return not throw_if_absent and not self._has_arg(arg)
-
-  def _set_value_arg(self, arg, value, throw_if_absent = True):
-    if self._should_fail_silently(arg, throw_if_absent):
-      return
-    assert(self._is_value_arg(arg))
-    i = self.target.args.index(arg)
-    self.target.args[i + 1] = value
-
-  def _update_value_arg(self, arg, func, throw_if_absent = True):
-    if self._should_fail_silently(arg, throw_if_absent):
-      return
-    self._set_value_arg(arg, func(self._get_value_arg(arg)))
-
-  def _set_arg_at(self, position, value):
-    self.target.args[position] = value
-
-  def _delete_value_arg(self, arg, throw_if_absent = True):
-    if self._should_fail_silently(arg, throw_if_absent):
-      return
-    assert(self._is_value_arg(arg))
-    i = self.target.args.index(arg)
-    self.target.args.pop(i)
-    self.target.args.pop(i)
-
-  def _append_arg(self, arg, value):
-    self.target.args.append(arg)
-    self.target.args.append(value)
-
-  def _sanitize_filepath_with_location_tag(self, arg):
-    if arg.startswith('../../'):
-      arg = self._sanitize_filepath(arg)
-      arg = self._add_location_tag(arg)
-    return arg
-
-  # wrap filename in location tag.
-  def _add_location_tag(self, filename):
-    return '$(location %s)' % filename
-
-  # applies common directory transformation that *should* be universally applicable.
-  # TODO: verify if it actually *is* universally applicable.
-  def _sanitize_filepath(self, filepath):
-    # Careful, order matters!
-    # delete all leading ../
-    filepath = re.sub('^(\.\./)+', '', filepath)
-    filepath = re.sub('^gen/jni_headers', '$(genDir)', filepath)
-    filepath = re.sub('^gen', '$(genDir)', filepath)
-    return filepath
-
-  # Iterate through all the args and apply function
-  def _update_all_args(self, func):
-    self.target.args = [func(arg) for arg in self.target.args]
-
-  def get_cmd(self):
-    arg_string = NEWLINE.join(self.target.args)
-    cmd = '$(location %s) %s' % (
-    gn_utils.label_to_path(self.target.script), arg_string)
-
-    if self.use_response_file:
-      # Pipe response file contents into script
-      cmd = 'echo \'%s\' |%s%s' % (self.target.response_file_contents, NEWLINE, cmd)
-    return cmd
-
-  def get_outputs(self):
-    return self.target.outputs
-
-  def get_srcs(self):
-    # gn treats inputs and sources for actions equally.
-    # soong only supports source files inside srcs, non-source files are added as
-    # tool_files dependency.
-    files = self.target.sources.union(self.target.inputs)
-    return {gn_utils.label_to_path(file) for file in files if is_supported_source_file(file)}
-
-  def get_tool_files(self):
-    # gn treats inputs and sources for actions equally.
-    # soong only supports source files inside srcs, non-source files are added as
-    # tool_files dependency.
-    files = self.target.sources.union(self.target.inputs)
-    tool_files = {gn_utils.label_to_path(file)
-                  for file in files if not is_supported_source_file(file)}
-    tool_files.add(gn_utils.label_to_path(self.target.script))
-    return tool_files
-
-  def _sanitize_args(self):
-    # Handle passing parameters via response file by piping them into the script
-    # and reading them from /dev/stdin.
-
-    self.use_response_file = gn_utils.RESPONSE_FILE in self.target.args
-    if self.use_response_file:
-      # Replace {{response_file_contents}} with /dev/stdin
-      self.target.args = ['/dev/stdin' if it == gn_utils.RESPONSE_FILE else it
-                          for it in self.target.args]
-
-  def _sanitize_outputs(self):
-    pass
-
-  def _sanitize_inputs(self):
-    pass
-
-  def sanitize(self):
-    self._sanitize_args()
-    self._sanitize_outputs()
-    self._sanitize_inputs()
-
-  # Whether this target generates header files
-  def is_header_generated(self):
-    return any(os.path.splitext(it)[1] == '.h' for it in self.target.outputs)
-
-class WriteBuildDateHeaderSanitizer(BaseActionSanitizer):
-  def _sanitize_args(self):
-    self._set_arg_at(0, '$(out)')
-    super()._sanitize_args()
-
-class WriteBuildFlagHeaderSanitizer(BaseActionSanitizer):
-  def _sanitize_args(self):
-    self._set_value_arg('--gen-dir', '.')
-    self._set_value_arg('--output', '$(out)')
-    super()._sanitize_args()
-
-class JniGeneratorSanitizer(BaseActionSanitizer):
-  def _add_location_tag_to_filepath(self, arg):
-    if not arg.endswith('.class'):
-      # --input_file supports both .class specifiers or source files as arguments.
-      # Only source files need to be wrapped inside a $(location <label>) tag.
-      arg = self._add_location_tag(arg)
-    return arg
-
-  def _sanitize_args(self):
-    self._update_value_arg('--jar_file', self._sanitize_filepath, False)
-    self._update_value_arg('--jar_file', self._add_location_tag, False)
-    if self._has_arg('--jar_file'):
-      self._append_arg('--javap', '$$(find out/.path -name javap)')
-    self._update_value_arg('--output_dir', self._sanitize_filepath)
-    self._update_value_arg('--includes', self._sanitize_filepath, False)
-    self._delete_value_arg('--prev_output_dir', False)
-    self._update_list_arg('--input_file', self._sanitize_filepath)
-    self._update_list_arg('--input_file', self._add_location_tag_to_filepath)
-    super()._sanitize_args()
-
-  def _sanitize_outputs(self):
-    # fix target.output directory to match #include statements.
-    self.target.outputs = {re.sub('^jni_headers/', '', out) for out in self.target.outputs}
-    super()._sanitize_outputs()
-
-  def get_tool_files(self):
-    tool_files = super().get_tool_files()
-    # android_jar.classes should be part of the tools as it list implicit classes
-    # for the script to generate JNI headers.
-    tool_files.add("base/android/jni_generator/android_jar.classes")
-    return tool_files
-
-class JniRegistrationGeneratorSanitizer(BaseActionSanitizer):
-  def _sanitize_inputs(self):
-    self.target.inputs = [file for file in self.target.inputs if not file.startswith('//out/')]
-
-  def _sanitize_args(self):
-    self._update_value_arg('--depfile', self._sanitize_filepath)
-    self._update_value_arg('--srcjar-path', self._sanitize_filepath)
-    self._update_value_arg('--header-path', self._sanitize_filepath)
-    self._set_value_arg('--sources-files', '$(genDir)/java.sources')
-    # update_jni_registration_module removes them from the srcs of the module
-    # It might be better to remove sources by '--sources-exclusions'
-    self._delete_value_arg('--sources-exclusions')
-    super()._sanitize_args()
-
-  def get_cmd(self):
-    # jni_registration_generator.py doesn't work with python2
-    cmd = "python3 " + super().get_cmd()
-    # Path in the original sources file does not work in genrule.
-    # So creating sources file in cmd based on the srcs of this target.
-    # Adding ../$(current_dir)/ to the head because jni_registration_generator.py uses the files
-    # whose path startswith(..)
-    commands = ["current_dir=`basename \\\`pwd\\\``;",
-                "for f in $(in);",
-                "do",
-                "echo \\\"../$$current_dir/$$f\\\" >> $(genDir)/java.sources;",
-                "done;",
-                cmd]
-
-    # .h file jni_registration_generator.py generates has #define with directory name.
-    # With the genrule env that contains "." which is invalid. So replace that at the end of cmd.
-    commands.append(";sed -i -e 's/OUT_SOONG_.TEMP_SBOX_.*_OUT/GEN/g' ")
-    commands.append("$(genDir)/components/cronet/android/cronet_jni_registration.h")
-    return NEWLINE.join(commands)
-
-class JavaJniRegistrationGeneratorSanitizer(JniRegistrationGeneratorSanitizer):
-  def get_name(self):
-    return label_to_module_name(self.target.name) + "__java"
-
-  def _sanitize_outputs(self):
-    self.target.outputs = [out for out in self.target.outputs if
-                           out.endswith(".srcjar")]
-    super()._sanitize_outputs()
-
-class VersionSanitizer(BaseActionSanitizer):
-  def _sanitize_args(self):
-    self._set_value_arg('-o', '$(out)')
-    # args for the version.py contain file path without leading --arg key. So apply sanitize
-    # function for all the args.
-    self._update_all_args(self._sanitize_filepath_with_location_tag)
-    self._set_value_arg('-e', "'%s'" % self._get_value_arg('-e'))
-    super()._sanitize_args()
-
-  def get_tool_files(self):
-    tool_files = super().get_tool_files()
-    # android_chrome_version.py is not specified in anywhere but version.py imports this file
-    tool_files.add('build/util/android_chrome_version.py')
-    return tool_files
-
-class JavaCppEnumSanitizer(BaseActionSanitizer):
-  def _sanitize_args(self):
-    self._update_all_args(self._sanitize_filepath_with_location_tag)
-    self._set_value_arg('--srcjar', '$(out)')
-    super()._sanitize_args()
-
-class MakeDafsaSanitizer(BaseActionSanitizer):
-  def is_header_generated(self):
-    # This script generates .cc files but they are #included by other sources
-    # (e.g. registry_controlled_domain.cc)
-    return True
-
-class JavaCppFeatureSanitizer(BaseActionSanitizer):
-  def _sanitize_args(self):
-    self._update_all_args(self._sanitize_filepath_with_location_tag)
-    self._set_value_arg('--srcjar', '$(out)')
-    super()._sanitize_args()
-
-class JavaCppStringSanitizer(BaseActionSanitizer):
-  def _sanitize_args(self):
-    self._update_all_args(self._sanitize_filepath_with_location_tag)
-    self._set_value_arg('--srcjar', '$(out)')
-    super()._sanitize_args()
-
-class WriteNativeLibrariesJavaSanitizer(BaseActionSanitizer):
-  def _sanitize_args(self):
-    self._set_value_arg('--output', '$(out)')
-    super()._sanitize_args()
-
-def get_action_sanitizer(target, type):
-  if target.script == "//build/write_buildflag_header.py":
-    return WriteBuildFlagHeaderSanitizer(target)
-  elif target.script == "//build/write_build_date_header.py":
-    return WriteBuildDateHeaderSanitizer(target)
-  elif target.script == '//base/android/jni_generator/jni_generator.py':
-    return JniGeneratorSanitizer(target)
-  elif target.script == '//base/android/jni_generator/jni_registration_generator.py':
-    if type == 'java_genrule':
-      return JavaJniRegistrationGeneratorSanitizer(target)
-    else:
-      return JniRegistrationGeneratorSanitizer(target)
-  elif target.script == "//build/util/version.py":
-    return VersionSanitizer(target)
-  elif target.script == "//build/android/gyp/java_cpp_enum.py":
-    return JavaCppEnumSanitizer(target)
-  elif target.script == "//net/tools/dafsa/make_dafsa.py":
-    return MakeDafsaSanitizer(target)
-  elif target.script == '//build/android/gyp/java_cpp_features.py':
-    return JavaCppFeatureSanitizer(target)
-  elif target.script == '//build/android/gyp/java_cpp_strings.py':
-    return JavaCppStringSanitizer(target)
-  elif target.script == '//build/android/gyp/write_native_libraries_java.py':
-    return WriteNativeLibrariesJavaSanitizer(target)
-  else:
-    # TODO: throw exception here once all script hacks have been converted.
-    return BaseActionSanitizer(target)
-
-def create_action_foreach_modules(blueprint, target):
-  """ The following assumes that rebase_path exists in the args.
-  The args of an action_foreach contains hints about which output files are generated
-  by which source files.
-  This is copied directly from the args
-  "gen/net/base/registry_controlled_domains/{{source_name_part}}-reversed-inc.cc"
-  So each source file will generate an output whose name is the {source_name-reversed-inc.cc}
-  """
-  new_args = []
-  for i, src in enumerate(sorted(target.sources)):
-    # don't add script arg for the first source -- create_action_module
-    # already does this.
-    if i != 0:
-      new_args.append('&& python3 $(location %s)' %
-                   gn_utils.label_to_path(target.script))
-    for arg in target.args:
-      if '{{source}}' in arg:
-        new_args.append('$(location %s)' % (gn_utils.label_to_path(src)))
-      elif '{{source_name_part}}' in arg:
-        source_name_part = src.split("/")[-1] # Get the file name only
-        source_name_part = source_name_part.split(".")[0] # Remove the extension (Ex: .cc)
-        file_name = arg.replace('{{source_name_part}}', source_name_part).split("/")[-1]
-        # file_name represent the output file name. But we need the whole path
-        # This can be found from target.outputs.
-        for out in target.outputs:
-          if out.endswith(file_name):
-            new_args.append('$(location %s)' % out)
-      else:
-        new_args.append(arg)
-
-  target.args = new_args
-  return create_action_module(blueprint, target, 'cc_genrule')
-
-def create_action_module(blueprint, target, type):
-  sanitizer = get_action_sanitizer(target, type)
-  sanitizer.sanitize()
-
-  module = Module(type, sanitizer.get_name(), target.name)
-  module.cmd = sanitizer.get_cmd()
-  module.out = sanitizer.get_outputs()
-  if sanitizer.is_header_generated():
-    module.genrule_headers.add(module.name)
-  module.srcs = sanitizer.get_srcs()
-  module.tool_files = sanitizer.get_tool_files()
-
-  blueprint.add_module(module)
-  return module
-
-
-
-def _get_cflags(cflags, defines):
-  cflags = {flag for flag in cflags if flag in cflag_allowlist}
-  # Consider proper allowlist or denylist if needed
-  cflags |= set("-D%s" % define.replace("\"", "\\\"") for define in defines)
-  return cflags
-
-def set_module_flags(module, cflags, defines):
-  module.cflags.update(_get_cflags(cflags, defines))
-  # TODO: implement proper cflag parsing.
-  for flag in cflags:
-    if '-std=' in flag:
-      module.cpp_std = flag[len('-std='):]
-    if '-fexceptions' in flag:
-      module.cppflags.add('-fexceptions')
-
-def set_module_include_dirs(module, cflags, include_dirs):
-  for flag in cflags:
-    if '-isystem' in flag:
-      module.local_include_dirs.add(flag[len('-isystem../../'):])
-
-  # Adding local_include_dirs is necessary due to source_sets / filegroups
-  # which do not properly propagate include directories.
-  # Filter any directory inside //out as a) this directory does not exist for
-  # aosp / soong builds and b) the include directory should already be
-  # configured via library dependency.
-  module.local_include_dirs.update([gn_utils.label_to_path(d)
-                                 for d in include_dirs
-                                 if not re.match('^//out/.*', d)])
-
-def create_modules_from_target(blueprint, gn, gn_target_name):
-  """Generate module(s) for a given GN target.
-
-    Given a GN target name, generate one or more corresponding modules into a
-    blueprint. The only case when this generates >1 module is proto libraries.
-
-    Args:
-        blueprint: Blueprint instance which is being generated.
-        gn: gn_utils.GnParser object.
-        gn_target_name: GN target for module generation.
-    """
-  bp_module_name = label_to_module_name(gn_target_name)
-  if bp_module_name in blueprint.modules:
-    return blueprint.modules[bp_module_name]
-  target = gn.get_target(gn_target_name)
-  log.info('create modules for %s (%s)', target.name, target.type)
-
-  if target.type == 'executable':
-    if target.testonly:
-      module_type = 'cc_test'
-    else:
-      # Can be used for both host and device targets.
-      module_type = 'cc_binary'
-    module = Module(module_type, bp_module_name, gn_target_name)
-  elif target.type == 'static_library':
-    module = Module('cc_library_static', bp_module_name, gn_target_name)
-  elif target.type == 'shared_library':
-    module = Module('cc_library_shared', bp_module_name, gn_target_name)
-  elif target.type == 'source_set':
-    module = Module('cc_object', bp_module_name, gn_target_name)
-  elif target.type == 'group':
-    # "group" targets are resolved recursively by gn_utils.get_target().
-    # There's nothing we need to do at this level for them.
-    return None
-  elif target.type == 'proto_library':
-    module = create_proto_modules(blueprint, gn, target)
-    if module is None:
-      return None
-  elif target.type == 'action':
-    module = create_action_module(blueprint, target, 'cc_genrule')
-  elif target.type == 'action_foreach':
-    module = create_action_foreach_modules(blueprint, target)
-  elif target.type == 'copy':
-    # TODO: careful now! copy targets are not supported yet, but this will stop
-    # traversing the dependency tree. For //base:base, this is not a big
-    # problem as libicu contains the only copy target which happens to be a
-    # leaf node.
-    return None
-  elif target.type == 'java_group':
-    # Java targets are handled outside of create_modules_from_target.
-    return None
-  else:
-    raise Error('Unknown target %s (%s)' % (target.name, target.type))
-
-  blueprint.add_module(module)
-  module.init_rc = target_initrc.get(target.name, [])
-  module.srcs.update(
-      gn_utils.label_to_path(src)
-      for src in target.sources
-      if is_supported_source_file(src) and not src.startswith("//out/test"))
-
-  # Add arch-specific properties
-  for arch_name, arch in target.arch.items():
-    module.target[arch_name].srcs.update(
-      gn_utils.label_to_path(src)
-      for src in arch.sources
-      if is_supported_source_file(src) and not src.startswith("//out/test"))
-
-  module.rtti = target.rtti
-
-  if target.type in gn_utils.LINKER_UNIT_TYPES:
-    set_module_flags(module, target.cflags, target.defines)
-    set_module_include_dirs(module, target.cflags, target.include_dirs)
-    # TODO: set_module_xxx is confusing, apply similar function to module and target in better way.
-    for arch_name, arch in target.arch.items():
-      set_module_flags(module.target[arch_name], arch.cflags, arch.defines)
-      # -Xclang -target-feature -Xclang +mte are used to enable MTE (Memory Tagging Extensions).
-      # Flags which does not start with '-' could not be in the cflags so enabling MTE by
-      # -march and -mcpu Feature Modifiers. MTE is only available on arm64. This is needed for
-      # building //base/allocator/partition_allocator:partition_alloc for arm64.
-      if '+mte' in arch.cflags and arch_name == 'android_arm64':
-        module.target[arch_name].cflags.add('-march=armv8-a+memtag')
-      set_module_include_dirs(module.target[arch_name], arch.cflags, arch.include_dirs)
-
-  module.host_supported = target.host_supported()
-  module.device_supported = target.device_supported()
-
-  if module.is_genrule():
-    module.apex_available.add(tethering_apex)
-
-  if module.is_compiled():
-    # Don't try to inject library/source dependencies into genrules or
-    # filegroups because they are not compiled in the traditional sense.
-    module.defaults = [defaults_module]
-    for lib in target.libs:
-      # Generally library names should be mangled as 'libXXX', unless they
-      # are HAL libraries (e.g., android.hardware.health@2.0) or AIDL c++ / NDK
-      # libraries (e.g. "android.hardware.power.stats-V1-cpp")
-      android_lib = lib if '@' in lib or "-cpp" in lib or "-ndk" in lib \
-        else 'lib' + lib
-      if lib in shared_library_allowlist:
-        module.add_android_shared_lib(android_lib)
-      if lib in static_library_allowlist:
-        module.add_android_static_lib(android_lib)
-
-    # Remove prohibited include directories
-    module.local_include_dirs = [d for d in module.local_include_dirs
-                                 if d not in local_include_dirs_denylist]
-
-  # If the module is a static library, export all the generated headers.
-  if module.type == 'cc_library_static':
-    module.export_generated_headers = module.generated_headers
-
-  if module.name == 'cronet_aml_components_cronet_android_cronet':
-    if target.output_name is None:
-      raise Error('Failed to get output_name for libcronet name')
-    # .so file name needs to match with CronetLibraryLoader.java (e.g. libcronet.109.0.5386.0.so)
-    # So setting the output name based on the output_name from the desc.json
-    module.stem = 'lib' + target.output_name
-
-  # dep_name is an unmangled GN target name (e.g. //foo:bar(toolchain)).
-  # Currently, only one module is generated from target even target has multiple toolchains.
-  # And module is generated based on the first visited target.
-  # Sort deps before iteration to make result deterministic.
-  all_deps = sorted(target.deps | target.source_set_deps | target.transitive_proto_deps)
-  for dep_name in all_deps:
-    # |builtin_deps| override GN deps with Android-specific ones. See the
-    # config in the top of this file.
-    if dep_name in builtin_deps:
-      builtin_deps[dep_name](module)
-      continue
-
-    dep_module = create_modules_from_target(blueprint, gn, dep_name)
-
-    if dep_module is None:
-      continue
-    # TODO: Proper dependency check for genrule.
-    # Currently, only propagating genrule dependencies.
-    # Also, currently, all the dependencies are propagated upwards.
-    # in gn, public_deps should be propagated but deps should not.
-    # Not sure this information is available in the desc.json.
-    # Following rule works for adding android_runtime_jni_headers to base:base.
-    # If this doesn't work for other target, hardcoding for specific target
-    # might be better.
-    if module.is_genrule() and dep_module.is_genrule():
-      module.genrule_headers.add(dep_module.name)
-      module.genrule_headers.update(dep_module.genrule_headers)
-
-    # For filegroups, and genrule, recurse but don't apply the
-    # deps.
-    if not module.is_compiled() or module.is_genrule():
-      continue
-
-    if dep_module.type == 'cc_library_shared':
-      module.shared_libs.add(dep_module.name)
-    elif dep_module.type == 'cc_library_static':
-      module.static_libs.add(dep_module.name)
-    elif dep_module.type == 'cc_object':
-      module.merge_attribute('generated_headers', dep_module, target.arch.keys())
-      if module.type != 'cc_object':
-        if dep_module.has_input_files():
-          # Only add it as part of srcs if the dep_module has input files otherwise
-          # this would throw an error.
-          module.srcs.add(":" + dep_module.name)
-        module.merge_attribute('export_generated_headers', dep_module,
-                         target.arch.keys(), 'generated_headers')
-    elif dep_module.type == 'cc_genrule':
-      module.merge_attribute('generated_headers', dep_module, [], 'genrule_headers')
-      module.merge_attribute('srcs', dep_module, [], 'genrule_srcs')
-      module.merge_attribute('shared_libs', dep_module, [], 'genrule_shared_libs')
-      module.merge_attribute('header_libs', dep_module, [], 'genrule_header_libs')
-      if module.type not in ["cc_object"]:
-        module.merge_attribute('export_generated_headers', dep_module, [],
-                         'genrule_headers')
-    elif dep_module.type == 'cc_binary':
-      continue  # Ignore executables deps (used by cmdline integration tests).
-    else:
-      raise Error('Unknown dep %s (%s) for target %s' %
-                  (dep_module.name, dep_module.type, module.name))
-
-  for arch_name, arch in target.arch.items():
-    for dep_name in arch.deps:
-      dep_module = create_modules_from_target(blueprint, gn, dep_name)
-      # Arch-specific dependencies currently only include cc_library_static.
-      # Revisit this approach once we need to support more target types.
-      if dep_module.type == 'cc_library_static':
-        module.target[arch_name].static_libs.add(dep_module.name)
-      elif dep_module.type == 'cc_genrule':
-        if dep_module.name.endswith(arch_name):
-          module.target[arch_name].generated_headers.update(dep_module.genrule_headers)
-          module.target[arch_name].srcs.update(dep_module.genrule_srcs)
-          module.target[arch_name].shared_libs.update(dep_module.genrule_shared_libs)
-          module.target[arch_name].header_libs.update(dep_module.genrule_header_libs)
-          if module.type not in ["cc_object"]:
-            module.target[arch_name].export_generated_headers.update(
-              dep_module.genrule_headers)
-      else:
-        raise Error('Unsupported arch-specific dependency %s of target %s with type %s' %
-                    (dep_module.name, target.name, dep_module.type))
-    for dep_name in arch.source_set_deps:
-      dep_module = create_modules_from_target(blueprint, gn, dep_name)
-      if dep_module.type == 'cc_object':
-        if module.type != 'cc_object':
-          # We only want to bubble up cc_objects for modules that are not cc_objects
-          # otherwise they'd be recompiled and that would cause multiple symbol redefinitions.
-          if dep_module.has_input_files():
-            # Only add it as part of srcs if the dep_module has input files otherwise
-            # this would throw an error.
-            module.target[arch_name].srcs.add(":" + dep_module.name)
-      else:
-        raise Error('Unsupported arch-specific dependency %s of target %s with type %s' %
-                    (dep_module.name, target.name, dep_module.type))
-  return module
-
-def create_java_jni_preprocessor(blueprint):
-  bp_module_name = module_prefix + 'java_jni_annotation_preprocessor'
-  module = Module('java_plugin', bp_module_name, '//base/android/jni_generator:jni_processor')
-  module.srcs.update(
-  [
-    "base/android/jni_generator/java/src/org/chromium/jni_generator/JniProcessor.java",
-    # Avoids a circular dependency with base:base_java. This is okay because
-    # no target should ever expect to package an annotation processor.
-    "build/android/java/src/org/chromium/build/annotations/CheckDiscard.java",
-    "build/android/java/src/org/chromium/build/annotations/MainDex.java",
-    "base/android/java/src/org/chromium/base/JniStaticTestMocker.java",
-    "base/android/java/src/org/chromium/base/NativeLibraryLoadedStatus.java",
-    "base/android/java/src/org/chromium/base/annotations/NativeMethods.java",
-    "base/android/java/src/org/chromium/base/JniException.java",
-    ":cronet_aml_build_android_build_config_gen",
-  ])
-  module.static_libs.update({
-      "javapoet",
-      "guava",
-      "auto_service_annotations",
-  })
-  module.processor_class = "org.chromium.jni_generator.JniProcessor"
-  blueprint.add_module(module)
-  return module
-
-def create_java_module(blueprint, gn):
-  bp_module_name = module_prefix + 'java'
-  module = Module('java_library', bp_module_name, '//gn:java')
-  module.srcs.update([gn_utils.label_to_path(source) for source in gn.java_sources])
-  module.libs = {
-    "androidx.annotation_annotation",
-    "jsr305",
-    "androidx.core_core-nodeps",
-    "androidx.collection_collection",
-    "androidx.annotation_annotation-experimental-nodeps",
-    "android-support-multidex",
-    "framework-connectivity.stubs.module_lib",
-    "framework-connectivity-t.stubs.module_lib",
-    "framework-tethering.stubs.module_lib",
-    "framework-wifi.stubs.module_lib",
-    "framework-mediaprovider.stubs.module_lib",
-  }
-  module.aidl["include_dirs"] = {"frameworks/base/core/java/"}
-  module.aidl["local_include_dirs"] = {"base/android/java/src/"}
-  module.sdk_version = "module_current"
-  module.apex_available.add(tethering_apex)
-  # TODO: remove following workaround required to make this module visible to make (b/203203405)
-  module.apex_available.add("//apex_available:platform")
-  for dep in gn.java_actions:
-    target = gn.get_target(dep)
-    if target.script == '//build/android/gyp/gcc_preprocess.py':
-      module.srcs.add(':' + create_gcc_preprocess_modules(blueprint, target).name)
-    else:
-      module.srcs.add(':' + create_action_module(blueprint, target, 'java_genrule').name)
-  preprocessor_module = create_java_jni_preprocessor(blueprint)
-  module.plugins.add(preprocessor_module.name)
-  blueprint.add_module(module)
-
-def update_jni_registration_module(module, gn):
-  # TODO: deny list is in the arg of jni_registration_generator.py. Should not be hardcoded
-  deny_list = [
-    '//base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java',
-    '//base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java',
-    '//base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java',
-    '//base/android/java/src/org/chromium/base/SysUtils.java']
-
-  # TODO: java_sources might not contain all the required java files
-  module.srcs.update([gn_utils.label_to_path(source)
-                      for source in gn.java_sources
-                      if source.endswith('.java') and source not in deny_list])
-
-def create_blueprint_for_targets(gn, targets):
-  """Generate a blueprint for a list of GN targets."""
-  blueprint = Blueprint()
-
-  # Default settings used by all modules.
-  defaults = Module('cc_defaults', defaults_module, '//gn:default_deps')
-  defaults.cflags = [
-      '-DGOOGLE_PROTOBUF_NO_RTTI',
-      '-Wno-error=return-type',
-      '-Wno-non-virtual-dtor',
-      '-Wno-macro-redefined',
-      '-Wno-missing-field-initializers',
-      '-Wno-sign-compare',
-      '-Wno-sign-promo',
-      '-Wno-unused-parameter',
-      '-Wno-null-pointer-subtraction', # Needed to libevent
-      '-Wno-deprecated-non-prototype', # needed for zlib
-      '-fvisibility=hidden',
-      '-Wno-ambiguous-reversed-operator', # needed for icui18n
-      '-Wno-unreachable-code-loop-increment', # needed for icui18n
-      '-O2',
-      '-fPIC',
-  ]
-  # Chromium builds do not add a dependency for headers found inside the
-  # sysroot, so they are added globally via defaults.
-  defaults.target['android'].header_libs = [
-      'media_ndk_headers',
-      'jni_headers',
-  ]
-  defaults.target['host'].cflags = [
-      # -DANDROID is added by default but target.defines contain -DANDROID if
-      # it's required.  So adding -UANDROID to cancel default -DANDROID if it's
-      # not specified.
-      # Note: -DANDROID is not consistently applied across the chromium code
-      # base, so it is removed unconditionally for host targets.
-      '-UANDROID',
-  ]
-  defaults.stl = 'none'
-  defaults.min_sdk_version = 29
-  defaults.apex_available.add(tethering_apex)
-  blueprint.add_module(defaults)
-
-  for target in targets:
-    create_modules_from_target(blueprint, gn, target)
-
-  create_java_module(blueprint, gn)
-  for module in blueprint.modules.values():
-    if 'cronet_jni_registration' in module.name:
-      update_jni_registration_module(module, gn)
-
-  # Merge in additional hardcoded arguments.
-  for module in blueprint.modules.values():
-    for key, add_val in additional_args.get(module.name, []):
-      curr = getattr(module, key)
-      if add_val and isinstance(add_val, set) and isinstance(curr, set):
-        curr.update(add_val)
-      elif isinstance(add_val, str) and (not curr or isinstance(curr, str)):
-        setattr(module, key, add_val)
-      elif isinstance(add_val, bool) and (not curr or isinstance(curr, bool)):
-        setattr(module, key, add_val)
-      elif isinstance(add_val, dict) and isinstance(curr, dict):
-        curr.update(add_val)
-      elif isinstance(add_val, dict) and isinstance(curr, Target):
-        curr.__dict__.update(add_val)
-      else:
-        raise Error('Unimplemented type %r of additional_args: %r' %
-                    (type(add_val), key))
-
-  return blueprint
-
-
-def main():
-  parser = argparse.ArgumentParser(
-      description='Generate Android.bp from a GN description.')
-  parser.add_argument(
-      '--desc',
-      help='GN description (e.g., gn desc out --format=json --all-toolchains "//*".' +
-           'You can specify multiple --desc options for different target_cpu',
-      required=True,
-      action='append'
-  )
-  parser.add_argument(
-      '--extras',
-      help='Extra targets to include at the end of the Blueprint file',
-      default=os.path.join(gn_utils.repo_root(), 'Android.bp.extras'),
-  )
-  parser.add_argument(
-      '--output',
-      help='Blueprint file to create',
-      default=os.path.join(gn_utils.repo_root(), 'Android.bp'),
-  )
-  parser.add_argument(
-      '-v',
-      '--verbose',
-      help='Print debug logs.',
-      action='store_true',
-  )
-  parser.add_argument(
-      'targets',
-      nargs=argparse.REMAINDER,
-      help='Targets to include in the blueprint (e.g., "//:perfetto_tests")'
-  )
-  args = parser.parse_args()
-
-  if args.verbose:
-    log.basicConfig(format='%(levelname)s:%(funcName)s:%(message)s', level=log.DEBUG)
-
-  targets = args.targets or default_targets
-  gn = gn_utils.GnParser()
-  for desc_file in args.desc:
-    with open(desc_file) as f:
-      desc = json.load(f)
-    for target in targets:
-      gn.parse_gn_desc(desc, target)
-  blueprint = create_blueprint_for_targets(gn, targets)
-  project_root = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
-  tool_name = os.path.relpath(os.path.abspath(__file__), project_root)
-
-  # Add any proto groups to the blueprint.
-  for l_name, t_names in proto_groups.items():
-    create_proto_group_modules(blueprint, gn, l_name, t_names)
-
-  output = [
-      """// 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.
-//
-// This file is automatically generated by %s. Do not edit.
-""" % (tool_name)
-  ]
-  blueprint.to_string(output)
-  if os.path.exists(args.extras):
-    with open(args.extras, 'r') as r:
-      for line in r:
-        output.append(line.rstrip("\n\r"))
-
-  out_files = []
-
-  # Generate the Android.bp file.
-  out_files.append(args.output + '.swp')
-  with open(out_files[-1], 'w') as f:
-    f.write('\n'.join(output))
-    # Text files should have a trailing EOL.
-    f.write('\n')
-
-  return 0
-
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/tools/gn2bp/gn_utils.py b/tools/gn2bp/gn_utils.py
deleted file mode 100644
index 6cbbd67..0000000
--- a/tools/gn2bp/gn_utils.py
+++ /dev/null
@@ -1,497 +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.
-
-# A collection of utilities for extracting build rule information from GN
-# projects.
-
-from __future__ import print_function
-import collections
-import errno
-import filecmp
-import json
-import logging as log
-import os
-import re
-import shutil
-import subprocess
-import sys
-
-BUILDFLAGS_TARGET = '//gn:gen_buildflags'
-GEN_VERSION_TARGET = '//src/base:version_gen_h'
-LINKER_UNIT_TYPES = ('executable', 'shared_library', 'static_library', 'source_set')
-JAVA_BANNED_SCRIPTS = [
-    "//build/android/gyp/turbine.py",
-    "//build/android/gyp/compile_java.py",
-    "//build/android/gyp/filter_zip.py",
-    "//build/android/gyp/dex.py",
-    "//build/android/gyp/write_build_config.py",
-    "//build/android/gyp/create_r_java.py",
-    "//build/android/gyp/ijar.py",
-    "//build/android/gyp/create_r_java.py",
-    "//build/android/gyp/bytecode_processor.py",
-    "//build/android/gyp/prepare_resources.py",
-    "//build/android/gyp/aar.py",
-    "//build/android/gyp/zip.py",
-]
-# TODO(primiano): investigate these, they require further componentization.
-ODR_VIOLATION_IGNORE_TARGETS = {
-    '//test/cts:perfetto_cts_deps',
-    '//:perfetto_integrationtests',
-}
-ARCH_REGEX = r'(android_x86_64|android_x86|android_arm|android_arm64|host)'
-RESPONSE_FILE = '{{response_file_name}}'
-
-def repo_root():
-  """Returns an absolute path to the repository root."""
-  return os.path.join(
-      os.path.realpath(os.path.dirname(__file__)), os.path.pardir)
-
-
-def label_to_path(label):
-  """Turn a GN output label (e.g., //some_dir/file.cc) into a path."""
-  assert label.startswith('//')
-  return label[2:] or "./"
-
-
-def label_without_toolchain(label):
-  """Strips the toolchain from a GN label.
-
-    Return a GN label (e.g //buildtools:protobuf(//gn/standalone/toolchain:
-    gcc_like_host) without the parenthesised toolchain part.
-    """
-  return label.split('(')[0]
-
-
-def label_to_target_name_with_path(label):
-  """
-  Turn a GN label into a target name involving the full path.
-  e.g., //src/perfetto:tests -> src_perfetto_tests
-  """
-  name = re.sub(r'^//:?', '', label)
-  name = re.sub(r'[^a-zA-Z0-9_]', '_', name)
-  return name
-
-def _is_java_source(src):
-  return os.path.splitext(src)[1] == '.java' and not src.startswith("//out/test/gen/")
-
-def is_java_action(script, outputs):
-  return (script != "" and script not in JAVA_BANNED_SCRIPTS) and any(
-      [file.endswith(".srcjar") or file.endswith(".java")
-       for file in outputs])
-
-class GnParser(object):
-  """A parser with some cleverness for GN json desc files
-
-    The main goals of this parser are:
-    1) Deal with the fact that other build systems don't have an equivalent
-       notion to GN's source_set. Conversely to Bazel's and Soong's filegroups,
-       GN source_sets expect that dependencies, cflags and other source_set
-       properties propagate up to the linker unit (static_library, executable or
-       shared_library). This parser simulates the same behavior: when a
-       source_set is encountered, some of its variables (cflags and such) are
-       copied up to the dependent targets. This is to allow gen_xxx to create
-       one filegroup for each source_set and then squash all the other flags
-       onto the linker unit.
-    2) Detect and special-case protobuf targets, figuring out the protoc-plugin
-       being used.
-    """
-
-  class Target(object):
-    """Reperesents A GN target.
-
-        Maked properties are propagated up the dependency chain when a
-        source_set dependency is encountered.
-        """
-    class Arch():
-      """Architecture-dependent properties
-        """
-      def __init__(self):
-        self.sources = set()
-        self.cflags = set()
-        self.defines = set()
-        self.include_dirs = set()
-        self.deps = set()
-        self.transitive_static_libs_deps = set()
-        self.source_set_deps = set()
-
-
-    def __init__(self, name, type):
-      self.name = name  # e.g. //src/ipc:ipc
-
-      VALID_TYPES = ('static_library', 'shared_library', 'executable', 'group',
-                     'action', 'source_set', 'proto_library', 'copy', 'action_foreach')
-      assert (type in VALID_TYPES)
-      self.type = type
-      self.testonly = False
-      self.toolchain = None
-
-      # These are valid only for type == proto_library.
-      # This is typically: 'proto', 'protozero', 'ipc'.
-      self.proto_plugin = None
-      self.proto_paths = set()
-      self.proto_exports = set()
-      self.proto_in_dir = ""
-
-      self.sources = set()
-      # TODO(primiano): consider whether the public section should be part of
-      # bubbled-up sources.
-      self.public_headers = set()  # 'public'
-
-      # These are valid only for type == 'action'
-      self.inputs = set()
-      self.outputs = set()
-      self.script = None
-      self.args = []
-      self.response_file_contents = None
-
-      # These variables are propagated up when encountering a dependency
-      # on a source_set target.
-      self.cflags = set()
-      self.defines = set()
-      self.deps = set()
-      self.libs = set()
-      self.include_dirs = set()
-      self.ldflags = set()
-      self.source_set_deps = set()  # Transitive set of source_set deps.
-      self.proto_deps = set()
-      self.transitive_proto_deps = set()
-      self.rtti = False
-
-      # TODO: come up with a better way to only run this once.
-      # is_finalized tracks whether finalize() was called on this target.
-      self.is_finalized = False
-      self.arch = dict()
-
-      # This is used to get the name/version of libcronet
-      self.output_name = None
-
-    def host_supported(self):
-      return 'host' in self.arch
-
-    def device_supported(self):
-      return any([name.startswith('android') for name in self.arch.keys()])
-
-    def __lt__(self, other):
-      if isinstance(other, self.__class__):
-        return self.name < other.name
-      raise TypeError(
-          '\'<\' not supported between instances of \'%s\' and \'%s\'' %
-          (type(self).__name__, type(other).__name__))
-
-    def __repr__(self):
-      return json.dumps({
-          k: (list(sorted(v)) if isinstance(v, set) else v)
-          for (k, v) in self.__dict__.items()
-      },
-                        indent=4,
-                        sort_keys=True)
-
-    def update(self, other, arch):
-      for key in ('cflags', 'defines', 'deps', 'include_dirs', 'ldflags',
-                  'source_set_deps', 'proto_deps', 'transitive_proto_deps',
-                  'libs', 'proto_paths'):
-        self.__dict__[key].update(other.__dict__.get(key, []))
-
-      for key_in_arch in ('cflags', 'defines', 'include_dirs', 'source_set_deps'):
-        self.arch[arch].__dict__[key_in_arch].update(
-          other.arch[arch].__dict__.get(key_in_arch, []))
-
-    def finalize(self):
-      """Move common properties out of arch-dependent subobjects to Target object.
-
-        TODO: find a better name for this function.
-        """
-      if self.is_finalized:
-        return
-      self.is_finalized = True
-
-      # Target contains the intersection of arch-dependent properties
-      self.sources = set.intersection(*[arch.sources for arch in self.arch.values()])
-      self.cflags = set.intersection(*[arch.cflags for arch in self.arch.values()])
-      self.defines = set.intersection(*[arch.defines for arch in self.arch.values()])
-      self.include_dirs = set.intersection(*[arch.include_dirs for arch in self.arch.values()])
-      self.deps.update(set.intersection(*[arch.deps for arch in self.arch.values()]))
-      self.source_set_deps.update(set.intersection(*[arch.source_set_deps for arch in self.arch.values()]))
-
-      # Deduplicate arch-dependent properties
-      for arch in self.arch.keys():
-        self.arch[arch].sources -= self.sources
-        self.arch[arch].cflags -= self.cflags
-        self.arch[arch].defines -= self.defines
-        self.arch[arch].include_dirs -= self.include_dirs
-        self.arch[arch].deps -= self.deps
-        self.arch[arch].source_set_deps -= self.source_set_deps
-
-
-  def __init__(self):
-    self.all_targets = {}
-    self.linker_units = {}  # Executables, shared or static libraries.
-    self.source_sets = {}
-    self.actions = {}
-    self.proto_libs = {}
-    self.java_sources = set()
-    self.java_actions = set()
-
-  def _get_response_file_contents(self, action_desc):
-    # response_file_contents are formatted as:
-    # ['--flags', '--flag=true && false'] and need to be formatted as:
-    # '--flags --flag=\"true && false\"'
-    flags = action_desc.get('response_file_contents', [])
-    formatted_flags = []
-    for flag in flags:
-      if '=' in flag:
-        key, val = flag.split('=')
-        formatted_flags.append('%s=\\"%s\\"' % (key, val))
-      else:
-        formatted_flags.append(flag)
-
-    return ' '.join(formatted_flags)
-
-  def _is_java_group(self, type_, target_name):
-    # Per https://chromium.googlesource.com/chromium/src/build/+/HEAD/android/docs/java_toolchain.md
-    # java target names must end in "_java".
-    # TODO: There are some other possible variations we might need to support.
-    return type_ == 'group' and re.match('.*_java$', target_name)
-
-  def _get_arch(self, toolchain):
-    if toolchain == '//build/toolchain/android:android_clang_x86':
-      return 'android_x86'
-    elif toolchain == '//build/toolchain/android:android_clang_x64':
-      return 'android_x86_64'
-    elif toolchain == '//build/toolchain/android:android_clang_arm':
-      return 'android_arm'
-    elif toolchain == '//build/toolchain/android:android_clang_arm64':
-      return 'android_arm64'
-    else:
-      return 'host'
-
-  def get_target(self, gn_target_name):
-    """Returns a Target object from the fully qualified GN target name.
-
-      get_target() requires that parse_gn_desc() has already been called.
-      """
-    # Run this every time as parse_gn_desc can be called at any time.
-    for target in self.all_targets.values():
-      target.finalize()
-
-    return self.all_targets[label_without_toolchain(gn_target_name)]
-
-  def parse_gn_desc(self, gn_desc, gn_target_name, is_java_target = False):
-    """Parses a gn desc tree and resolves all target dependencies.
-
-        It bubbles up variables from source_set dependencies as described in the
-        class-level comments.
-        """
-    # Use name without toolchain for targets to support targets built for
-    # multiple archs.
-    target_name = label_without_toolchain(gn_target_name)
-    desc = gn_desc[gn_target_name]
-    type_ = desc['type']
-    arch = self._get_arch(desc['toolchain'])
-
-    is_java_target = is_java_target or self._is_java_group(type_, target_name)
-
-    # Action modules can differ depending on the target architecture, yet
-    # genrule's do not allow to overload cmd per target OS / arch.  Create a
-    # separate action for every architecture.
-    # Cover both action and action_foreach
-    if type_.startswith('action') and not is_java_target:
-      # Don't meddle with the java actions name
-      target_name += '__' + arch
-
-    target = self.all_targets.get(target_name)
-    if target is None:
-      target = GnParser.Target(target_name, type_)
-      self.all_targets[target_name] = target
-
-    if arch not in target.arch:
-      target.arch[arch] = GnParser.Target.Arch()
-    else:
-      return target  # Target already processed.
-
-    target.testonly = desc.get('testonly', False)
-
-    proto_target_type, proto_desc = self.get_proto_target_type(gn_desc, gn_target_name)
-    if proto_target_type is not None:
-      self.proto_libs[target.name] = target
-      target.type = 'proto_library'
-      target.proto_plugin = proto_target_type
-      target.proto_paths.update(self.get_proto_paths(proto_desc))
-      target.proto_exports.update(self.get_proto_exports(proto_desc))
-      target.proto_in_dir = self.get_proto_in_dir(proto_desc)
-      for gn_proto_deps_name in proto_desc.get('deps', []):
-        dep = self.parse_gn_desc(gn_desc, gn_proto_deps_name)
-        target.deps.add(dep.name)
-      target.arch[arch].sources.update(proto_desc.get('sources', []))
-      assert (all(x.endswith('.proto') for x in target.arch[arch].sources))
-    elif target.type == 'source_set':
-      self.source_sets[gn_target_name] = target
-      target.arch[arch].sources.update(desc.get('sources', []))
-    elif target.type in LINKER_UNIT_TYPES:
-      self.linker_units[gn_target_name] = target
-      target.arch[arch].sources.update(desc.get('sources', []))
-    elif (desc.get("script", "") in JAVA_BANNED_SCRIPTS
-          or self._is_java_group(target.type, target.name)):
-      # java_group identifies the group target generated by the android_library
-      # or java_library template. A java_group must not be added as a dependency, but sources are collected
-      log.debug('Found java target %s', target.name)
-      if target.type == "action":
-        # Convert java actions into java_group and keep the inputs for collection.
-        target.inputs.update(desc.get('inputs', []))
-      target.type = 'java_group'
-    elif target.type in ['action', 'action_foreach']:
-      self.actions[gn_target_name] = target
-      target.inputs.update(desc.get('inputs', []))
-      target.arch[arch].sources.update(desc.get('sources', []))
-      outs = [re.sub('^//out/.+?/gen/', '', x) for x in desc['outputs']]
-      target.outputs.update(outs)
-      target.script = desc['script']
-      target.args = desc['args']
-      target.response_file_contents = self._get_response_file_contents(desc)
-    elif target.type == 'copy':
-      # TODO: copy rules are not currently implemented.
-      self.actions[gn_target_name] = target
-
-    # Default for 'public' is //* - all headers in 'sources' are public.
-    # TODO(primiano): if a 'public' section is specified (even if empty), then
-    # the rest of 'sources' is considered inaccessible by gn. Consider
-    # emulating that, so that generated build files don't end up with overly
-    # accessible headers.
-    public_headers = [x for x in desc.get('public', []) if x != '*']
-    target.public_headers.update(public_headers)
-
-    target.arch[arch].cflags.update(desc.get('cflags', []) + desc.get('cflags_cc', []))
-    target.libs.update(desc.get('libs', []))
-    target.ldflags.update(desc.get('ldflags', []))
-    target.arch[arch].defines.update(desc.get('defines', []))
-    target.arch[arch].include_dirs.update(desc.get('include_dirs', []))
-    target.output_name = desc.get('output_name', None)
-    if "-frtti" in target.arch[arch].cflags:
-      target.rtti = True
-
-    # Recurse in dependencies.
-    for gn_dep_name in desc.get('deps', []):
-      dep = self.parse_gn_desc(gn_desc, gn_dep_name, is_java_target)
-      if dep.type == 'proto_library':
-        target.proto_deps.add(dep.name)
-        target.transitive_proto_deps.add(dep.name)
-        target.proto_paths.update(dep.proto_paths)
-        target.transitive_proto_deps.update(dep.transitive_proto_deps)
-      elif dep.type == 'source_set':
-        target.arch[arch].source_set_deps.add(dep.name)
-        target.arch[arch].source_set_deps.update(dep.arch[arch].source_set_deps)
-      elif dep.type == 'group':
-        target.update(dep, arch)  # Bubble up groups's cflags/ldflags etc.
-      elif dep.type in ['action', 'action_foreach', 'copy']:
-        if proto_target_type is None:
-          target.arch[arch].deps.add(dep.name)
-      elif dep.type in LINKER_UNIT_TYPES:
-        target.arch[arch].deps.add(dep.name)
-      elif dep.type == 'java_group':
-        # Explicitly break dependency chain when a java_group is added.
-        # Java sources are collected and eventually compiled as one large
-        # java_library.
-        pass
-
-      # Source set bubble up transitive source sets but can't be combined with this
-      # if they are combined then source sets will bubble up static libraries
-      # while we only want to have source sets bubble up only source sets.
-      if dep.type == 'static_library':
-        # Bubble up static_libs. Necessary, since soong does not propagate
-        # static_libs up the build tree.
-        target.arch[arch].transitive_static_libs_deps.add(dep.name)
-
-      if arch in dep.arch:
-        target.arch[arch].transitive_static_libs_deps.update(
-            dep.arch[arch].transitive_static_libs_deps)
-        target.arch[arch].deps.update(target.arch[arch].transitive_static_libs_deps)
-
-      # Collect java sources. Java sources are kept inside the __compile_java target.
-      # This target can be used for both host and target compilation; only add
-      # the sources if they are destined for the target (i.e. they are a
-      # dependency of the __dex target)
-      # Note: this skips prebuilt java dependencies. These will have to be
-      # added manually when building the jar.
-      if target.name.endswith('__dex'):
-        if dep.name.endswith('__compile_java'):
-          log.debug('Adding java sources for %s', dep.name)
-          java_srcs = [src for src in dep.inputs if _is_java_source(src)]
-          self.java_sources.update(java_srcs)
-      if dep.type in ["action"] and target.type == "java_group":
-        # //base:base_java_aidl generates srcjar from .aidl files. But java_library in soong can
-        # directly have .aidl files in srcs. So adding .aidl files to the java_sources.
-        # TODO: Find a better way/place to do this.
-        if dep.name == '//base:base_java_aidl':
-          self.java_sources.update(dep.arch[arch].sources)
-        else:
-          self.java_actions.add(dep.name)
-    return target
-
-  def get_proto_exports(self, proto_desc):
-    # exports in metadata will be available for source_set targets.
-    metadata = proto_desc.get('metadata', {})
-    return metadata.get('exports', [])
-
-  def get_proto_paths(self, proto_desc):
-    # import_dirs in metadata will be available for source_set targets.
-    metadata = proto_desc.get('metadata', {})
-    return metadata.get('import_dirs', [])
-
-
-  def get_proto_in_dir(self, proto_desc):
-    args = proto_desc.get('args')
-    return re.sub('^\.\./\.\./', '', args[args.index('--proto-in-dir') + 1])
-
-  def get_proto_target_type(self, gn_desc, gn_target_name):
-    """ Checks if the target is a proto library and return the plugin.
-
-        Returns:
-            (None, None): if the target is not a proto library.
-            (plugin, proto_desc) where |plugin| is 'proto' in the default (lite)
-            case or 'protozero' or 'ipc' or 'descriptor'; |proto_desc| is the GN
-            json desc of the target with the .proto sources (_gen target for
-            non-descriptor types or the target itself for descriptor type).
-        """
-    parts = gn_target_name.split('(', 1)
-    name = parts[0]
-    toolchain = '(' + parts[1] if len(parts) > 1 else ''
-
-    # Descriptor targets don't have a _gen target; instead we look for the
-    # characteristic flag in the args of the target itself.
-    desc = gn_desc.get(gn_target_name)
-    if '--descriptor_set_out' in desc.get('args', []):
-      return 'descriptor', desc
-
-    # Source set proto targets have a non-empty proto_library_sources in the
-    # metadata of the description.
-    metadata = desc.get('metadata', {})
-    if 'proto_library_sources' in metadata:
-      return 'source_set', desc
-
-    # In all other cases, we want to look at the _gen target as that has the
-    # important information.
-    gen_desc = gn_desc.get('%s_gen%s' % (name, toolchain))
-    if gen_desc is None or gen_desc['type'] != 'action':
-      return None, None
-    if gen_desc['script'] != '//tools/protoc_wrapper/protoc_wrapper.py':
-      return None, None
-    plugin = 'proto'
-    args = gen_desc.get('args', [])
-    for arg in (arg for arg in args if arg.startswith('--plugin=')):
-      # |arg| at this point looks like:
-      #  --plugin=protoc-gen-plugin=gcc_like_host/protozero_plugin
-      # or
-      #  --plugin=protoc-gen-plugin=protozero_plugin
-      plugin = arg.split('=')[-1].split('/')[-1].replace('_plugin', '')
-    return plugin, gen_desc
diff --git a/tools/gn2bp/update_results.sh b/tools/gn2bp/update_results.sh
deleted file mode 100755
index a464604..0000000
--- a/tools/gn2bp/update_results.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/bash
-
-# This script is expected to run after gen_android_bp is modified.
-#
-#   ./update_result.sh
-#
-# TARGETS contains targets which are supported by gen_android_bp and
-# this script generates Android.bp.swp from TARGETS.
-# This makes it easy to realize unintended impact/degression on
-# previously supported targets.
-
-set -eux
-
-BASEDIR=$(dirname "$0")
-$BASEDIR/gen_android_bp --desc $BASEDIR/desc_x64.json --desc $BASEDIR/desc_x86.json \
---desc $BASEDIR/desc_arm.json --desc $BASEDIR/desc_arm64.json --out $BASEDIR/Android.bp