Merge "link libbase statically into libnetd_updatable - saves ~85 kB"
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 721f05e..ea3f8d6 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -52,7 +52,7 @@
         first: {
             jni_libs: [
                 "libservice-connectivity",
-                "libcom_android_connectivity_com_android_net_module_util_jni",
+                "libandroid_net_connectivity_com_android_net_module_util_jni",
             ],
             native_shared_libs: ["libnetd_updatable"],
         },
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index abcfbeb..b5aedeb 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -26,7 +26,9 @@
 // The above defaults can be used to disable framework-connectivity t
 // targets while minimizing merge conflicts in the build rules.
 
-
+// SDK library for connectivity bootclasspath classes that were part of the non-updatable API before
+// T, and were moved to the module in T. Other bootclasspath classes in connectivity should go to
+// framework-connectivity.
 java_sdk_library {
     name: "framework-connectivity-tiramisu",
     sdk_version: "module_current",
@@ -37,14 +39,40 @@
     ],
     srcs: [
         ":framework-connectivity-tiramisu-updatable-sources",
+        ":framework-nearby-java-sources",
+    ],
+    // Do not add static_libs to this library: put them in framework-connectivity instead.
+    // The jarjar rules are only so that references to jarjared utils in
+    // framework-connectivity-pre-jarjar match at runtime.
+    jarjar_rules: ":connectivity-jarjar-rules",
+    stub_only_libs: [
+        // Use prebuilt framework-connectivity stubs to avoid circular dependencies
+        "sdk_module-lib_current_framework-connectivity",
     ],
     libs: [
         "unsupportedappusage",
         "app-compat-annotations",
+        "sdk_module-lib_current_framework-connectivity",
+    ],
+    impl_only_libs: [
+        // The build system will use framework-bluetooth module_current stubs, because
+        // of sdk_version: "module_current" above.
+        "framework-bluetooth",
+        // Compile against the entire implementation of framework-connectivity,
+        // including hidden methods. This is safe because if framework-connectivity-t is
+        // on the bootclasspath (i.e., T), then framework-connectivity is also on the
+        // bootclasspath (because it shipped in S).
+        //
+        // This compiles against the pre-jarjar target so that this code can use
+        // non-jarjard names of widely-used packages such as com.android.net.module.util.
+        "framework-connectivity-pre-jarjar",
     ],
     permitted_packages: [
         "android.net",
         "android.net.nsd",
+        "android.nearby",
+        "com.android.connectivity",
+        "com.android.nearby",
     ],
     apex_available: [
         "com.android.tethering",
@@ -54,10 +82,12 @@
         // In preparation for future move
         "//packages/modules/Connectivity/apex",
         "//packages/modules/Connectivity/service-t",
+        "//packages/modules/Nearby/service",
         "//frameworks/base",
 
         // Tests using hidden APIs
         "//cts/tests/netlegacy22.api",
+        "//cts/tests/tests/app.usage", // NetworkUsageStatsTest
         "//external/sl4a:__subpackages__",
         "//frameworks/libs/net/common/testutils",
         "//frameworks/libs/net/common/tests:__subpackages__",
@@ -66,6 +96,7 @@
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
         "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/NetworkStack/tests:__subpackages__",
+        "//packages/modules/Nearby/tests:__subpackages__",
         "//packages/modules/Wifi/service/tests/wifitests",
     ],
 }
diff --git a/framework/Android.bp b/framework/Android.bp
index de505c7..d3e46fa 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -55,12 +55,11 @@
     ],
 }
 
-java_sdk_library {
-    name: "framework-connectivity",
+java_defaults {
+    name: "framework-connectivity-defaults",
+    defaults: ["framework-module-defaults"],
     sdk_version: "module_current",
     min_sdk_version: "30",
-    defaults: ["framework-module-defaults"],
-    installable: true,
     srcs: [
         ":framework-connectivity-sources",
         ":net-utils-framework-common-srcs",
@@ -76,6 +75,9 @@
             "frameworks/native/aidl/binder", // For PersistableBundle.aidl
         ],
     },
+    stub_only_libs: [
+        "framework-connectivity-tiramisu.stubs.module_lib",
+    ],
     impl_only_libs: [
         "framework-tethering.stubs.module_lib",
         "framework-wifi.stubs.module_lib",
@@ -83,16 +85,42 @@
     ],
     static_libs: [
         "modules-utils-build",
+        "modules-utils-preconditions",
     ],
     libs: [
+        "framework-connectivity-tiramisu.stubs.module_lib",
         "unsupportedappusage",
     ],
-    jarjar_rules: "jarjar-rules.txt",
+    apex_available: [
+        "com.android.tethering",
+    ],
+    lint: { strict_updatability_linting: true },
+}
+
+java_library {
+    name: "framework-connectivity-pre-jarjar",
+    defaults: ["framework-connectivity-defaults"],
+    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.,
+        // TetheringRequest depends on LinkAddress).
+        "framework-tethering.stubs.module_lib",
+    ],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"]
+}
+
+java_sdk_library {
+    name: "framework-connectivity",
+    defaults: ["framework-connectivity-defaults"],
+    installable: true,
+    jarjar_rules: ":connectivity-jarjar-rules",
     permitted_packages: ["android.net"],
     impl_library_visibility: [
         "//packages/modules/Connectivity/Tethering/apex",
         // In preparation for future move
         "//packages/modules/Connectivity/apex",
+        "//packages/modules/Connectivity/framework-t",
         "//packages/modules/Connectivity/service",
         "//packages/modules/Connectivity/service-t",
         "//frameworks/base/packages/Connectivity/service",
@@ -100,6 +128,7 @@
 
         // Tests using hidden APIs
         "//cts/tests/netlegacy22.api",
+        "//cts/tests/tests/app.usage", // NetworkUsageStatsTest
         "//external/sl4a:__subpackages__",
         "//frameworks/base/packages/Connectivity/tests:__subpackages__",
         "//frameworks/libs/net/common/testutils",
@@ -111,10 +140,6 @@
         "//packages/modules/NetworkStack/tests:__subpackages__",
         "//packages/modules/Wifi/service/tests/wifitests",
     ],
-    apex_available: [
-        "com.android.tethering",
-    ],
-    lint: { strict_updatability_linting: true },
 }
 
 cc_library_shared {
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 5cfab5d..5961e72 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -43,9 +43,11 @@
     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_POWERSAVE = 3; // 0x3
     field public static final int FIREWALL_CHAIN_RESTRICTED = 4; // 0x4
     field public static final int FIREWALL_CHAIN_STANDBY = 2; // 0x2
diff --git a/framework/jarjar-rules.txt b/framework/jarjar-rules.txt
deleted file mode 100644
index ae6b9c3..0000000
--- a/framework/jarjar-rules.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-rule com.android.net.module.util.** android.net.connectivity.framework.util.@1
-rule com.android.modules.utils.** android.net.connectivity.framework.modules.utils.@1
-rule android.net.NetworkFactory* android.net.connectivity.framework.NetworkFactory@1
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 7593482..92ede91 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -877,6 +877,15 @@
     public static final int BLOCKED_REASON_LOCKDOWN_VPN = 1 << 4;
 
     /**
+     * Flag to indicate that an app is subject to Low Power Standby restrictions that would
+     * result in its network access being blocked.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final int BLOCKED_REASON_LOW_POWER_STANDBY = 1 << 5;
+
+    /**
      * Flag to indicate that an app is subject to Data saver restrictions that would
      * result in its metered network access being blocked.
      *
@@ -914,6 +923,7 @@
             BLOCKED_REASON_APP_STANDBY,
             BLOCKED_REASON_RESTRICTED_MODE,
             BLOCKED_REASON_LOCKDOWN_VPN,
+            BLOCKED_REASON_LOW_POWER_STANDBY,
             BLOCKED_METERED_REASON_DATA_SAVER,
             BLOCKED_METERED_REASON_USER_RESTRICTED,
             BLOCKED_METERED_REASON_ADMIN_DISABLED,
@@ -963,13 +973,22 @@
     @SystemApi(client = MODULE_LIBRARIES)
     public static final int FIREWALL_CHAIN_RESTRICTED = 4;
 
+    /**
+     * Firewall chain used for low power standby.
+     * Allowlist of apps that have access in low power standby.
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = false, prefix = "FIREWALL_CHAIN_", value = {
         FIREWALL_CHAIN_DOZABLE,
         FIREWALL_CHAIN_STANDBY,
         FIREWALL_CHAIN_POWERSAVE,
-        FIREWALL_CHAIN_RESTRICTED
+        FIREWALL_CHAIN_RESTRICTED,
+        FIREWALL_CHAIN_LOW_POWER_STANDBY
     })
     public @interface FirewallChain {}
 
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 4254a66..feb9fc1 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -621,22 +621,22 @@
      * Network capabilities that are expected to be mutable, i.e., can change while a particular
      * network is connected.
      */
-    private static final long MUTABLE_CAPABILITIES =
+    private static final long MUTABLE_CAPABILITIES = NetworkCapabilitiesUtils.packBitList(
             // TRUSTED can change when user explicitly connects to an untrusted network in Settings.
             // http://b/18206275
-            (1 << NET_CAPABILITY_TRUSTED)
-            | (1 << NET_CAPABILITY_VALIDATED)
-            | (1 << NET_CAPABILITY_CAPTIVE_PORTAL)
-            | (1 << NET_CAPABILITY_NOT_ROAMING)
-            | (1 << NET_CAPABILITY_FOREGROUND)
-            | (1 << NET_CAPABILITY_NOT_CONGESTED)
-            | (1 << NET_CAPABILITY_NOT_SUSPENDED)
-            | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY)
-            | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED)
-            | (1 << NET_CAPABILITY_NOT_VCN_MANAGED)
+            NET_CAPABILITY_TRUSTED,
+            NET_CAPABILITY_VALIDATED,
+            NET_CAPABILITY_CAPTIVE_PORTAL,
+            NET_CAPABILITY_NOT_ROAMING,
+            NET_CAPABILITY_FOREGROUND,
+            NET_CAPABILITY_NOT_CONGESTED,
+            NET_CAPABILITY_NOT_SUSPENDED,
+            NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+            NET_CAPABILITY_TEMPORARILY_NOT_METERED,
+            NET_CAPABILITY_NOT_VCN_MANAGED,
             // The value of NET_CAPABILITY_HEAD_UNIT is 32, which cannot use int to do bit shift,
             // otherwise there will be an overflow. Use long to do bit shift instead.
-            | (1L << NET_CAPABILITY_HEAD_UNIT);
+            NET_CAPABILITY_HEAD_UNIT);
 
     /**
      * Network capabilities that are not allowed in NetworkRequests. This exists because the
@@ -650,25 +650,26 @@
     // in an infinite loop about these.
     private static final long NON_REQUESTABLE_CAPABILITIES =
             MUTABLE_CAPABILITIES
-            & ~(1 << NET_CAPABILITY_TRUSTED)
-            & ~(1 << NET_CAPABILITY_NOT_VCN_MANAGED);
+            & ~(1L << NET_CAPABILITY_TRUSTED)
+            & ~(1L << NET_CAPABILITY_NOT_VCN_MANAGED);
 
     /**
      * Capabilities that are set by default when the object is constructed.
      */
-    private static final long DEFAULT_CAPABILITIES =
-            (1 << NET_CAPABILITY_NOT_RESTRICTED)
-            | (1 << NET_CAPABILITY_TRUSTED)
-            | (1 << NET_CAPABILITY_NOT_VPN);
+    private static final long DEFAULT_CAPABILITIES = NetworkCapabilitiesUtils.packBitList(
+            NET_CAPABILITY_NOT_RESTRICTED,
+            NET_CAPABILITY_TRUSTED,
+            NET_CAPABILITY_NOT_VPN);
 
     /**
      * Capabilities that are managed by ConnectivityService.
      */
     private static final long CONNECTIVITY_MANAGED_CAPABILITIES =
-            (1 << NET_CAPABILITY_VALIDATED)
-            | (1 << NET_CAPABILITY_CAPTIVE_PORTAL)
-            | (1 << NET_CAPABILITY_FOREGROUND)
-            | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY);
+            NetworkCapabilitiesUtils.packBitList(
+                    NET_CAPABILITY_VALIDATED,
+                    NET_CAPABILITY_CAPTIVE_PORTAL,
+                    NET_CAPABILITY_FOREGROUND,
+                    NET_CAPABILITY_PARTIAL_CONNECTIVITY);
 
     /**
      * Capabilities that are allowed for test networks. This list must be set so that it is safe
@@ -677,14 +678,15 @@
      * INTERNET, IMS, SUPL, etc.
      */
     private static final long TEST_NETWORKS_ALLOWED_CAPABILITIES =
-            (1 << NET_CAPABILITY_NOT_METERED)
-            | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED)
-            | (1 << NET_CAPABILITY_NOT_RESTRICTED)
-            | (1 << NET_CAPABILITY_NOT_VPN)
-            | (1 << NET_CAPABILITY_NOT_ROAMING)
-            | (1 << NET_CAPABILITY_NOT_CONGESTED)
-            | (1 << NET_CAPABILITY_NOT_SUSPENDED)
-            | (1 << NET_CAPABILITY_NOT_VCN_MANAGED);
+            NetworkCapabilitiesUtils.packBitList(
+            NET_CAPABILITY_NOT_METERED,
+            NET_CAPABILITY_TEMPORARILY_NOT_METERED,
+            NET_CAPABILITY_NOT_RESTRICTED,
+            NET_CAPABILITY_NOT_VPN,
+            NET_CAPABILITY_NOT_ROAMING,
+            NET_CAPABILITY_NOT_CONGESTED,
+            NET_CAPABILITY_NOT_SUSPENDED,
+            NET_CAPABILITY_NOT_VCN_MANAGED);
 
     /**
      * Adds the given capability to this {@code NetworkCapability} instance.
@@ -1156,12 +1158,13 @@
     /**
      * Allowed transports on an unrestricted test network (in addition to TRANSPORT_TEST).
      */
-    private static final int UNRESTRICTED_TEST_NETWORKS_ALLOWED_TRANSPORTS =
-            1 << TRANSPORT_TEST
-            // Test ethernet networks can be created with EthernetManager#setIncludeTestInterfaces
-            | 1 << TRANSPORT_ETHERNET
-            // Test VPN networks can be created but their UID ranges must be empty.
-            | 1 << TRANSPORT_VPN;
+    private static final long UNRESTRICTED_TEST_NETWORKS_ALLOWED_TRANSPORTS =
+            NetworkCapabilitiesUtils.packBitList(
+                    TRANSPORT_TEST,
+                    // Test eth networks are created with EthernetManager#setIncludeTestInterfaces
+                    TRANSPORT_ETHERNET,
+                    // Test VPN networks can be created but their UID ranges must be empty.
+                    TRANSPORT_VPN);
 
     /**
      * Adds the given transport type to this {@code NetworkCapability} instance.
@@ -3076,4 +3079,4 @@
             return new NetworkCapabilities(mCaps);
         }
     }
-}
+}
\ No newline at end of file
diff --git a/nearby/Android.bp b/nearby/Android.bp
new file mode 100644
index 0000000..baa0740
--- /dev/null
+++ b/nearby/Android.bp
@@ -0,0 +1,39 @@
+//
+// 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 {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Empty sources and libraries to avoid merge conflicts with downstream
+// branches
+// TODO: remove once the Nearby sources are available in this branch
+filegroup {
+    name: "framework-nearby-java-sources",
+    srcs: [],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
+
+
+java_library {
+    name: "service-nearby",
+    srcs: [],
+    sdk_version: "module_current",
+    min_sdk_version: "30",
+    apex_available: ["com.android.tethering"],
+    visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
diff --git a/service-t/Android.bp b/service-t/Android.bp
index 48c74c6..f33be63 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -36,15 +36,17 @@
     ],
     libs: [
         "framework-annotations-lib",
-        "framework-connectivity.impl",
+        "framework-connectivity-pre-jarjar",
         "framework-connectivity-tiramisu.impl",
         "service-connectivity-pre-jarjar",
         "unsupportedappusage",
     ],
     static_libs: [
-        "modules-utils-build",
+        // Do not add static_libs here if they are already included in framework-connectivity
+        // or in service-connectivity. They are not necessary (included via
+        // service-connectivity-pre-jarjar), and in the case of code that is already in
+        // framework-connectivity, the classes would be included in the apex twice.
         "modules-utils-statemachine",
-        "net-utils-framework-common",
     ],
     apex_available: [
         "com.android.tethering",
diff --git a/service/Android.bp b/service/Android.bp
index 1e66c11..6d187ba 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -20,9 +20,9 @@
 }
 
 // The library name match the service-connectivity jarjar rules that put the JNI utils in the
-// com.android.connectivity.com.android.net.module.util package.
+// android.net.connectivity.com.android.net.module.util package.
 cc_library_shared {
-    name: "libcom_android_connectivity_com_android_net_module_util_jni",
+    name: "libandroid_net_connectivity_com_android_net_module_util_jni",
     min_sdk_version: "30",
     cflags: [
         "-Wall",
@@ -98,20 +98,20 @@
     ],
     libs: [
         "framework-annotations-lib",
-        "framework-connectivity.impl",
+        "framework-connectivity-pre-jarjar",
         "framework-tethering.stubs.module_lib",
         "framework-wifi.stubs.module_lib",
         "unsupportedappusage",
         "ServiceConnectivityResources",
     ],
     static_libs: [
+        // Do not add libs here if they are already included
+        // in framework-connectivity
         "dnsresolver_aidl_interface-V9-java",
-        "modules-utils-build",
         "modules-utils-shell-command-handler",
         "net-utils-device-common",
         "net-utils-device-common-bpf",
         "net-utils-device-common-netlink",
-        "net-utils-framework-common",
         "netd-client",
         "networkstack-client",
         "PlatformProperties",
@@ -157,8 +157,9 @@
     static_libs: [
         "service-connectivity-pre-jarjar",
         "service-connectivity-tiramisu-pre-jarjar",
+        "service-nearby",
     ],
-    jarjar_rules: "jarjar-rules.txt",
+    jarjar_rules: ":connectivity-jarjar-rules",
     apex_available: [
         "com.android.tethering",
     ],
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index f658a5e..e3b26fd 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -1,6 +1,15 @@
+# Classes in framework-connectivity are restricted to the android.net package.
+# This cannot be changed because it is harcoded in ART in S.
+# Any missing jarjar rule for framework-connectivity would be caught by the
+# build as an unexpected class outside of the android.net package.
+rule com.android.net.module.util.** android.net.connectivity.@0
+rule com.android.modules.utils.** android.net.connectivity.@0
+rule android.net.NetworkFactory* android.net.connectivity.@0
+
+# From modules-utils-preconditions
+rule com.android.internal.util.Preconditions* android.net.connectivity.@0
+
 rule android.sysprop.** com.android.connectivity.@0
-rule com.android.net.module.util.** com.android.connectivity.@0
-rule com.android.modules.utils.** com.android.connectivity.@0
 
 # internal util classes from framework-connectivity-shared-srcs
 rule android.util.LocalLog* com.android.connectivity.@0
@@ -23,9 +32,6 @@
 rule android.net.ResolverParamsParcel* com.android.connectivity.@0
 # Also includes netd event listener AIDL, but this is handled by netd-client rules
 
-# From net-utils-device-common
-rule android.net.NetworkFactory* com.android.connectivity.@0
-
 # From netd-client (newer AIDLs should go to android.net.netd.aidl)
 rule android.net.netd.aidl.** com.android.connectivity.@0
 # Avoid including android.net.INetdEventCallback, used in tests but not part of the module
diff --git a/service/jni/com_android_net_module_util/onload.cpp b/service/jni/com_android_net_module_util/onload.cpp
index 07ae31c..2f09e55 100644
--- a/service/jni/com_android_net_module_util/onload.cpp
+++ b/service/jni/com_android_net_module_util/onload.cpp
@@ -30,10 +30,10 @@
     }
 
     if (register_com_android_net_module_util_BpfMap(env,
-            "com/android/connectivity/com/android/net/module/util/BpfMap") < 0) return JNI_ERR;
+            "android/net/connectivity/com/android/net/module/util/BpfMap") < 0) return JNI_ERR;
 
     if (register_com_android_net_module_util_TcUtils(env,
-            "com/android/connectivity/com/android/net/module/util/TcUtils") < 0) return JNI_ERR;
+            "android/net/connectivity/com/android/net/module/util/TcUtils") < 0) return JNI_ERR;
 
     return JNI_VERSION_1_6;
 }
diff --git a/service/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
index bde52a5..2aaa4c3 100644
--- a/service/jni/com_android_server_BpfNetMaps.cpp
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -39,10 +39,9 @@
 namespace android {
 
 static void native_init(JNIEnv* env, jobject clazz) {
-  // start is still being called by netd
-  Status status = mTc.initMaps();
+  Status status = mTc.start();
    if (!isOk(status)) {
-    ALOGE("%s failed", __func__);
+    ALOGE("%s failed, error code = %d", __func__, status.code());
   }
 }
 
@@ -51,7 +50,7 @@
   Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
       TrafficController::IptOp::IptOpInsert);
   if (!isOk(status)) {
-    ALOGE("%s failed, errer code = %d", __func__, status.code());
+    ALOGE("%s failed, error code = %d", __func__, status.code());
   }
   return (jint)status.code();
 }
@@ -61,7 +60,7 @@
   Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
       TrafficController::IptOp::IptOpDelete);
   if (!isOk(status)) {
-    ALOGE("%s failed, errer code = %d", __func__, status.code());
+    ALOGE("%s failed, error code = %d", __func__, status.code());
   }
   return (jint)status.code();
 }
@@ -71,7 +70,7 @@
   Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
       TrafficController::IptOp::IptOpInsert);
   if (!isOk(status)) {
-    ALOGE("%s failed, errer code = %d", __func__, status.code());
+    ALOGE("%s failed, error code = %d", __func__, status.code());
   }
   return (jint)status.code();
 }
@@ -81,7 +80,7 @@
   Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
       TrafficController::IptOp::IptOpDelete);
   if (!isOk(status)) {
-    ALOGD("%s failed, errer code = %d", __func__, status.code());
+    ALOGD("%s failed, error code = %d", __func__, status.code());
   }
   return (jint)status.code();
 }
diff --git a/service/native/Android.bp b/service/native/Android.bp
index 0e805e2..a20c54f 100644
--- a/service/native/Android.bp
+++ b/service/native/Android.bp
@@ -70,6 +70,7 @@
         "libnetdutils",
         "libtraffic_controller",
         "libutils",
+        "libnetd_updatable",
         "netd_aidl_interface-lateststable-ndk",
     ],
 }
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index f401636..39f3365 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -38,6 +38,7 @@
 
 #include "TrafficController.h"
 #include "bpf/BpfUtils.h"
+#include "NetdUpdatablePublic.h"
 
 using namespace android::bpf;  // NOLINT(google-build-using-namespace): grandfathered
 
@@ -746,5 +747,133 @@
     expectPrivilegedUserSetEmpty();
 }
 
+constexpr uint32_t SOCK_CLOSE_WAIT_US = 30 * 1000;
+constexpr uint32_t ENOBUFS_POLL_WAIT_US = 10 * 1000;
+
+using android::base::Error;
+using android::base::Result;
+using android::bpf::BpfMap;
+
+// This test set up a SkDestroyListener that is running parallel with the production
+// SkDestroyListener. The test will create thousands of sockets and tag them on the
+// production cookieUidTagMap and close them in a short time. When the number of
+// sockets get closed exceeds the buffer size, it will start to return ENOBUFF
+// error. The error will be ignored by the production SkDestroyListener and the
+// test will clean up the tags in tearDown if there is any remains.
+
+// TODO: Instead of test the ENOBUFF error, we can test the production
+// SkDestroyListener to see if it failed to delete a tagged socket when ENOBUFF
+// triggered.
+class NetlinkListenerTest : public testing::Test {
+  protected:
+    NetlinkListenerTest() {}
+    BpfMap<uint64_t, UidTagValue> mCookieTagMap;
+
+    void SetUp() {
+        mCookieTagMap.reset(android::bpf::mapRetrieveRW(COOKIE_TAG_MAP_PATH));
+        ASSERT_TRUE(mCookieTagMap.isValid());
+    }
+
+    void TearDown() {
+        const auto deleteTestCookieEntries = [](const uint64_t& key, const UidTagValue& value,
+                                                BpfMap<uint64_t, UidTagValue>& map) {
+            if ((value.uid == TEST_UID) && (value.tag == TEST_TAG)) {
+                Result<void> res = map.deleteValue(key);
+                if (res.ok() || (res.error().code() == ENOENT)) {
+                    return Result<void>();
+                }
+                ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s\n", key,
+                      strerror(res.error().code()));
+            }
+            // Move forward to next cookie in the map.
+            return Result<void>();
+        };
+        EXPECT_RESULT_OK(mCookieTagMap.iterateWithValue(deleteTestCookieEntries));
+    }
+
+    Result<void> checkNoGarbageTagsExist() {
+        const auto checkGarbageTags = [](const uint64_t&, const UidTagValue& value,
+                                         const BpfMap<uint64_t, UidTagValue>&) -> Result<void> {
+            if ((TEST_UID == value.uid) && (TEST_TAG == value.tag)) {
+                return Error(EUCLEAN) << "Closed socket is not untagged";
+            }
+            return {};
+        };
+        return mCookieTagMap.iterateWithValue(checkGarbageTags);
+    }
+
+    bool checkMassiveSocketDestroy(int totalNumber, bool expectError) {
+        std::unique_ptr<android::netdutils::NetlinkListenerInterface> skDestroyListener;
+        auto result = android::net::TrafficController::makeSkDestroyListener();
+        if (!isOk(result)) {
+            ALOGE("Unable to create SkDestroyListener: %s", toString(result).c_str());
+        } else {
+            skDestroyListener = std::move(result.value());
+        }
+        int rxErrorCount = 0;
+        // Rx handler extracts nfgenmsg looks up and invokes registered dispatch function.
+        const auto rxErrorHandler = [&rxErrorCount](const int, const int) { rxErrorCount++; };
+        skDestroyListener->registerSkErrorHandler(rxErrorHandler);
+        int fds[totalNumber];
+        for (int i = 0; i < totalNumber; i++) {
+            fds[i] = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
+            // The likely reason for a failure is running out of available file descriptors.
+            EXPECT_LE(0, fds[i]) << i << " of " << totalNumber;
+            if (fds[i] < 0) {
+                // EXPECT_LE already failed above, so test case is a failure, but we don't
+                // want potentially tens of thousands of extra failures creating and then
+                // closing all these fds cluttering up the logs.
+                totalNumber = i;
+                break;
+            };
+            libnetd_updatable_tagSocket(fds[i], TEST_TAG, TEST_UID, 1000);
+        }
+
+        // TODO: Use a separate thread that has its own fd table so we can
+        // close sockets even faster simply by terminating that thread.
+        for (int i = 0; i < totalNumber; i++) {
+            EXPECT_EQ(0, close(fds[i]));
+        }
+        // wait a bit for netlink listener to handle all the messages.
+        usleep(SOCK_CLOSE_WAIT_US);
+        if (expectError) {
+            // If ENOBUFS triggered, check it only called into the handler once, ie.
+            // that the netlink handler is not spinning.
+            int currentErrorCount = rxErrorCount;
+            // 0 error count is acceptable because the system has chances to close all sockets
+            // normally.
+            EXPECT_LE(0, rxErrorCount);
+            if (!rxErrorCount) return true;
+
+            usleep(ENOBUFS_POLL_WAIT_US);
+            EXPECT_EQ(currentErrorCount, rxErrorCount);
+        } else {
+            EXPECT_RESULT_OK(checkNoGarbageTagsExist());
+            EXPECT_EQ(0, rxErrorCount);
+        }
+        return false;
+    }
+};
+
+TEST_F(NetlinkListenerTest, TestAllSocketUntagged) {
+    checkMassiveSocketDestroy(10, false);
+    checkMassiveSocketDestroy(100, false);
+}
+
+// Disabled because flaky on blueline-userdebug; this test relies on the main thread
+// winning a race against the NetlinkListener::run() thread. There's no way to ensure
+// things will be scheduled the same way across all architectures and test environments.
+TEST_F(NetlinkListenerTest, DISABLED_TestSkDestroyError) {
+    bool needRetry = false;
+    int retryCount = 0;
+    do {
+        needRetry = checkMassiveSocketDestroy(32500, true);
+        if (needRetry) retryCount++;
+    } while (needRetry && retryCount < 3);
+    // Should review test if it can always close all sockets correctly.
+    EXPECT_GT(3, retryCount);
+}
+
+
 }  // namespace net
 }  // namespace android
diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h
index 2d89344..ddcf445 100644
--- a/service/native/include/TrafficController.h
+++ b/service/native/include/TrafficController.h
@@ -32,22 +32,14 @@
 using netdutils::StatusOr;
 
 class TrafficController {
-    // TODO: marking this private for right now, as start is already called by
-    // netd. start() calls initMaps(), initPrograms(), and sets up the socket
-    // destroy listener. Both initPrograms() and setting up the socket destroy
-    // listener should only be done once.
+  public:
+    static constexpr char DUMP_KEYWORD[] = "trafficcontroller";
+
     /*
      * Initialize the whole controller
      */
     netdutils::Status start();
 
-  public:
-    static constexpr char DUMP_KEYWORD[] = "trafficcontroller";
-
-    // TODO: marking this public for right now, as start() is already called by
-    // netd.
-    netdutils::Status initMaps() EXCLUDES(mMutex);
-
     int setCounterSet(int counterSetNum, uid_t uid, uid_t callingUid) EXCLUDES(mMutex);
 
     /*
@@ -195,6 +187,8 @@
 
     std::mutex mMutex;
 
+    netdutils::Status initMaps() EXCLUDES(mMutex);
+
     // Keep track of uids that have permission UPDATE_DEVICE_STATS so we don't
     // need to call back to system server for permission check.
     std::set<uid_t> mPrivilegedUser GUARDED_BY(mMutex);
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index e444a12..7a3bab3 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -22,6 +22,8 @@
 import android.system.Os;
 import android.util.Log;
 
+import com.android.modules.utils.build.SdkLevel;
+
 /**
  * BpfNetMaps is responsible for providing traffic controller relevant functionality.
  *
@@ -30,134 +32,118 @@
 public class BpfNetMaps {
     private static final String TAG = "BpfNetMaps";
     private final INetd mNetd;
-    // TODO: change USE_JNI to SdkLevel.isAtLeastT()
-    private static final boolean USE_JNI = false;
+    // Use legacy netd for releases before T.
+    private static final boolean USE_NETD = !SdkLevel.isAtLeastT();
+    private static boolean sInitialized = false;
 
-    static {
-        if (USE_JNI) {
-            System.loadLibrary("traffic_controller_jni");
+    /**
+     * Initializes the class if it is not already initialized. This method will open maps but not
+     * cause any other effects. This method may be called multiple times on any thread.
+     */
+    private static synchronized void ensureInitialized() {
+        if (sInitialized) return;
+        if (!USE_NETD) {
+            System.loadLibrary("service-connectivity");
             native_init();
         }
+        sInitialized = true;
     }
 
     public BpfNetMaps(INetd netd) {
+        ensureInitialized();
         mNetd = netd;
     }
 
-   /**
-    * Add naughty app bandwidth rule for specific app
-    *
-    * @param uid uid of target app
-    * @throws ServiceSpecificException in case of failure, with an error code indicating the
-    *         cause of the failure.
-    */
-    public void addNaughtyApp(final int uid) {
-        if (!USE_JNI) {
-            try {
-                mNetd.bandwidthAddNaughtyApp(uid);
-            } catch (RemoteException e) {
-                throw new IllegalStateException(e);
-            }
+    private void maybeThrow(final int err, final String msg) {
+        if (err != 0) {
+            throw new ServiceSpecificException(err, msg + ": " + Os.strerror(err));
+        }
+    }
+
+    /**
+     * Add naughty app bandwidth rule for specific app
+     *
+     * @param uid uid of target app
+     * @throws RemoteException when netd has crashed.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    public void addNaughtyApp(final int uid) throws RemoteException {
+        if (USE_NETD) {
+            mNetd.bandwidthAddNaughtyApp(uid);
             return;
         }
         final int err = native_addNaughtyApp(uid);
-        if (err != 0) {
-            throw new ServiceSpecificException(err, "Unable to add naughty app: "
-                            + Os.strerror(err));
-        }
+        maybeThrow(err, "Unable to add naughty app");
     }
 
-   /**
-    * Remove naughty app bandwidth rule for specific app
-    *
-    * @param uid uid of target app
-    * @throws ServiceSpecificException in case of failure, with an error code indicating the
-    *         cause of the failure.
-    */
-    public void removeNaughtyApp(final int uid) {
-        if (!USE_JNI) {
-            try {
-                mNetd.bandwidthRemoveNaughtyApp(uid);
-            } catch (RemoteException e) {
-                throw new IllegalStateException(e);
-            }
+    /**
+     * Remove naughty app bandwidth rule for specific app
+     *
+     * @param uid uid of target app
+     * @throws RemoteException when netd has crashed.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    public void removeNaughtyApp(final int uid) throws RemoteException {
+        if (USE_NETD) {
+            mNetd.bandwidthRemoveNaughtyApp(uid);
             return;
         }
         final int err = native_removeNaughtyApp(uid);
-        if (err != 0) {
-            throw new ServiceSpecificException(err, "Unable to remove naughty app: "
-                            + Os.strerror(err));
-        }
+        maybeThrow(err, "Unable to remove naughty app");
     }
 
-   /**
-    * Add nice app bandwidth rule for specific app
-    *
-    * @param uid uid of target app
-    * @throws ServiceSpecificException in case of failure, with an error code indicating the
-    *         cause of the failure.
-    */
-    public void addNiceApp(final int uid) {
-        if (!USE_JNI) {
-            try {
-                mNetd.bandwidthAddNiceApp(uid);
-            } catch (RemoteException e) {
-                throw new IllegalStateException(e);
-            }
+    /**
+     * Add nice app bandwidth rule for specific app
+     *
+     * @param uid uid of target app
+     * @throws RemoteException when netd has crashed.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    public void addNiceApp(final int uid) throws RemoteException {
+        if (USE_NETD) {
+            mNetd.bandwidthAddNiceApp(uid);
             return;
         }
         final int err = native_addNiceApp(uid);
-        if (err != 0) {
-            throw new ServiceSpecificException(err, "Unable to add nice app: "
-                            + Os.strerror(err));
-        }
+        maybeThrow(err, "Unable to add nice app");
     }
 
-   /**
-    * Remove nice app bandwidth rule for specific app
-    *
-    * @param uid uid of target app
-    * @throws ServiceSpecificException in case of failure, with an error code indicating the
-    *         cause of the failure.
-    */
-    public void removeNiceApp(final int uid) {
-        if (!USE_JNI) {
-            try {
-                mNetd.bandwidthRemoveNiceApp(uid);
-            } catch (RemoteException e) {
-                throw new IllegalStateException(e);
-            }
+    /**
+     * Remove nice app bandwidth rule for specific app
+     *
+     * @param uid uid of target app
+     * @throws RemoteException when netd has crashed.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    public void removeNiceApp(final int uid) throws RemoteException {
+        if (USE_NETD) {
+            mNetd.bandwidthRemoveNiceApp(uid);
             return;
         }
         final int err = native_removeNiceApp(uid);
-        if (err != 0) {
-            throw new ServiceSpecificException(err, "Unable to remove nice app: "
-                            + Os.strerror(err));
-        }
+        maybeThrow(err, "Unable to remove nice app");
     }
 
-   /**
-    * Set target firewall child chain
-    *
-    * @param childChain target chain to enable
-    * @param enable whether to enable or disable child chain.
-    * @throws ServiceSpecificException in case of failure, with an error code indicating the
-    *         cause of the failure.
-    */
-    public void setChildChain(final int childChain, final boolean enable) {
-        if (!USE_JNI) {
-            try {
-                mNetd.firewallEnableChildChain(childChain, enable);
-            } catch (RemoteException e) {
-                throw new IllegalStateException(e);
-            }
+    /**
+     * Set target firewall child chain
+     *
+     * @param childChain target chain to enable
+     * @param enable     whether to enable or disable child chain.
+     * @throws RemoteException when netd has crashed.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    public void setChildChain(final int childChain, final boolean enable) throws RemoteException {
+        if (USE_NETD) {
+            mNetd.firewallEnableChildChain(childChain, enable);
             return;
         }
         final int err = native_setChildChain(childChain, enable);
-        if (err != 0) {
-            throw new ServiceSpecificException(-err, "Unable to set child chain: "
-                            + Os.strerror(-err));
-        }
+        maybeThrow(err, "Unable to set child chain");
     }
 
     /**
@@ -165,22 +151,19 @@
      *
      * The chain may be an allowlist chain or a denylist chain. A denylist chain contains DROP
      * rules for the specified UIDs and a RETURN rule at the end. An allowlist chain contains RETURN
-     * rules for the system UID range (0 to {@code UID_APP} - 1), RETURN rules for for the specified
+     * rules for the system UID range (0 to {@code UID_APP} - 1), RETURN rules for the specified
      * UIDs, and a DROP rule at the end. The chain will be created if it does not exist.
      *
-     * @param chainName The name of the chain to replace.
+     * @param chainName   The name of the chain to replace.
      * @param isAllowlist Whether this is an allowlist or denylist chain.
-     * @param uids The list of UIDs to allow/deny.
-     * @return true if the chain was successfully replaced, false otherwise.
+     * @param uids        The list of UIDs to allow/deny.
+     * @return 0 if the chain was successfully replaced, errno otherwise.
+     * @throws RemoteException when netd has crashed.
      */
     public int replaceUidChain(final String chainName, final boolean isAllowlist,
-            final int[] uids) {
-        if (!USE_JNI) {
-            try {
-                mNetd.firewallReplaceUidChain(chainName, isAllowlist, uids);
-            } catch (RemoteException e) {
-                throw new IllegalStateException(e);
-            }
+            final int[] uids) throws RemoteException {
+        if (USE_NETD) {
+            mNetd.firewallReplaceUidChain(chainName, isAllowlist, uids);
             return 0;
         }
         final int err = native_replaceUidChain(chainName, isAllowlist, uids);
@@ -190,29 +173,24 @@
         return -err;
     }
 
-   /**
-    * Set firewall rule for uid
-    *
-    * @param childChain target chain
-    * @param uid uid to allow/deny
-    * @param firewallRule either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
-    * @throws ServiceSpecificException in case of failure, with an error code indicating the
-    *         cause of the failure.
-    */
-    public void setUidRule(final int childChain, final int uid, final int firewallRule) {
-        if (!USE_JNI) {
-            try {
-                mNetd.firewallSetUidRule(childChain, uid, firewallRule);
-            } catch (RemoteException e) {
-                throw new IllegalStateException(e);
-            }
+    /**
+     * Set firewall rule for uid
+     *
+     * @param childChain   target chain
+     * @param uid          uid to allow/deny
+     * @param firewallRule either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
+     * @throws RemoteException when netd has crashed.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    public void setUidRule(final int childChain, final int uid, final int firewallRule)
+            throws RemoteException {
+        if (USE_NETD) {
+            mNetd.firewallSetUidRule(childChain, uid, firewallRule);
             return;
         }
         final int err = native_setUidRule(childChain, uid, firewallRule);
-        if (err != 0) {
-            throw new ServiceSpecificException(-err, "Unable to set uid rule: "
-                            + Os.strerror(-err));
-        }
+        maybeThrow(err, "Unable to set uid rule");
     }
 
     /**
@@ -226,25 +204,19 @@
      * instead. Otherwise calling this method will not affect existing rules set on other UIDs.
      *
      * @param ifName the name of the interface on which the filtering rules will allow packets to
-              be received.
-     * @param uids an array of UIDs which the filtering rules will be set
+     *               be received.
+     * @param uids   an array of UIDs which the filtering rules will be set
+     * @throws RemoteException when netd has crashed.
      * @throws ServiceSpecificException in case of failure, with an error code indicating the
-     *         cause of the failure.
+     *                                  cause of the failure.
      */
-    public void addUidInterfaceRules(final String ifName, final int[] uids) {
-        if (!USE_JNI) {
-            try {
-                mNetd.firewallAddUidInterfaceRules(ifName, uids);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Exception when updating permissions: " + e);
-            }
+    public void addUidInterfaceRules(final String ifName, final int[] uids) throws RemoteException {
+        if (USE_NETD) {
+            mNetd.firewallAddUidInterfaceRules(ifName, uids);
             return;
         }
         final int err = native_addUidInterfaceRules(ifName, uids);
-        if (err != 0) {
-            throw new ServiceSpecificException(err, "Unable to add uid interface rules: "
-                            + Os.strerror(err));
-        }
+        maybeThrow(err, "Unable to add uid interface rules");
     }
 
     /**
@@ -254,62 +226,48 @@
      * by addUidInterfaceRules(). Ignore any uid which does not have filtering rule.
      *
      * @param uids an array of UIDs from which the filtering rules will be removed
+     * @throws RemoteException when netd has crashed.
      * @throws ServiceSpecificException in case of failure, with an error code indicating the
-     *         cause of the failure.
+     *                                  cause of the failure.
      */
-    public void removeUidInterfaceRules(final int[] uids) {
-        if (!USE_JNI) {
-            try {
-                mNetd.firewallRemoveUidInterfaceRules(uids);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Exception when updating permissions: " + e);
-            }
+    public void removeUidInterfaceRules(final int[] uids) throws RemoteException {
+        if (USE_NETD) {
+            mNetd.firewallRemoveUidInterfaceRules(uids);
             return;
         }
         final int err = native_removeUidInterfaceRules(uids);
-        if (err != 0) {
-            throw new ServiceSpecificException(err, "Unable to remove uid interface rules: "
-                            + Os.strerror(err));
-        }
+        maybeThrow(err, "Unable to remove uid interface rules");
     }
 
-   /**
-    * Request netd to change the current active network stats map.
-    * @throws ServiceSpecificException in case of failure, with an error code indicating the
-    *         cause of the failure.
-    */
-    public void swapActiveStatsMap() {
-        if (!USE_JNI) {
-            try {
-                mNetd.trafficSwapActiveStatsMap();
-            } catch (RemoteException e) {
-                throw new IllegalStateException(e);
-            }
+    /**
+     * Request netd to change the current active network stats map.
+     *
+     * @throws RemoteException when netd has crashed.
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
+     */
+    public void swapActiveStatsMap() throws RemoteException {
+        if (USE_NETD) {
+            mNetd.trafficSwapActiveStatsMap();
             return;
         }
         final int err = native_swapActiveStatsMap();
-        if (err != 0) {
-            throw new ServiceSpecificException(err, "Unable to swap active stats map: "
-                            + Os.strerror(err));
-        }
+        maybeThrow(err, "Unable to swap active stats map");
     }
 
-   /**
-    * Assigns android.permission.INTERNET and/or android.permission.UPDATE_DEVICE_STATS to the uids
-    * specified. Or remove all permissions from the uids.
-    *
-    * @param permission The permission to grant, it could be either PERMISSION_INTERNET and/or
-    *                   PERMISSION_UPDATE_DEVICE_STATS. If the permission is NO_PERMISSIONS, then
-    *                   revoke all permissions for the uids.
-    * @param uids uid of users to grant permission
-    */
-    public void setNetPermForUids(final int permissions, final int[] uids) {
-        if (!USE_JNI) {
-            try {
-                mNetd.trafficSetNetPermForUids(permissions, uids);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Pass appId list of special permission failed." + e);
-            }
+    /**
+     * Assigns android.permission.INTERNET and/or android.permission.UPDATE_DEVICE_STATS to the uids
+     * specified. Or remove all permissions from the uids.
+     *
+     * @param permissions The permission to grant, it could be either PERMISSION_INTERNET and/or
+     *                    PERMISSION_UPDATE_DEVICE_STATS. If the permission is NO_PERMISSIONS, then
+     *                    revoke all permissions for the uids.
+     * @param uids        uid of users to grant permission
+     * @throws RemoteException when netd has crashed.
+     */
+    public void setNetPermForUids(final int permissions, final int[] uids) throws RemoteException {
+        if (USE_NETD) {
+            mNetd.trafficSetNetPermForUids(permissions, uids);
             return;
         }
         native_setPermissionForUids(permissions, uids);
@@ -319,27 +277,26 @@
      * Set counter set for uid
      *
      * @param counterSet either SET_DEFAULT or SET_FOREGROUND
-     * @param uid uid to foreground/background
+     * @param uid        uid to foreground/background
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
      */
-    public int setCounterSet(final int counterSet, final int uid) {
+    public void setCounterSet(final int counterSet, final int uid) {
         final int err = native_setCounterSet(counterSet, uid);
-        if (err != 0) {
-            Log.e(TAG, "setCounterSet failed: " + Os.strerror(-err));
-        }
-        return -err;
+        maybeThrow(err, "setCounterSet failed");
     }
 
     /**
      * Reset Uid stats
+     *
      * @param tag default 0
      * @param uid given uid to be clear
+     * @throws ServiceSpecificException in case of failure, with an error code indicating the
+     *                                  cause of the failure.
      */
-    public int deleteTagData(final int tag, final int uid) {
+    public void deleteTagData(final int tag, final int uid) {
         final int err = native_deleteTagData(tag, uid);
-        if (err != 0) {
-            Log.e(TAG, "deleteTagData failed: " + Os.strerror(-err));
-        }
-        return -err;
+        maybeThrow(err, "deleteTagData failed");
     }
 
     private static native void native_init();
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index a453270..a9a06e4 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -1339,6 +1339,18 @@
         }
 
         /**
+         * @see CarrierPrivilegeAuthenticator
+         */
+        public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
+                @NonNull final Context context, @NonNull final TelephonyManager tm) {
+            if (SdkLevel.isAtLeastT()) {
+                return new CarrierPrivilegeAuthenticator(context, tm);
+            } else {
+                return null;
+            }
+        }
+
+        /**
          * @see DeviceConfigUtils#isFeatureEnabled
          */
         public boolean isFeatureEnabled(Context context, String name, boolean defaultEnabled) {
@@ -1426,12 +1438,8 @@
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
-        if (SdkLevel.isAtLeastT()) {
-            mCarrierPrivilegeAuthenticator =
-                    new CarrierPrivilegeAuthenticator(mContext, mTelephonyManager);
-        } else {
-            mCarrierPrivilegeAuthenticator = null;
-        }
+        mCarrierPrivilegeAuthenticator =
+                mDeps.makeCarrierPrivilegeAuthenticator(mContext, mTelephonyManager);
 
         // To ensure uid state is synchronized with Network Policy, register for
         // NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
@@ -4157,11 +4165,11 @@
         }
     }
 
-    private boolean hasCarrierPrivilegeForNetworkRequest(int callingUid,
-            NetworkRequest networkRequest) {
+    private boolean hasCarrierPrivilegeForNetworkCaps(final int callingUid,
+            @NonNull final NetworkCapabilities caps) {
         if (SdkLevel.isAtLeastT() && mCarrierPrivilegeAuthenticator != null) {
-            return mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(callingUid,
-                    networkRequest);
+            return mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                    callingUid, caps);
         }
         return false;
     }
@@ -4205,7 +4213,7 @@
                     }
                 }
                 if (req.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
-                    if (!hasCarrierPrivilegeForNetworkRequest(nri.mUid, req)
+                    if (!hasCarrierPrivilegeForNetworkCaps(nri.mUid, req.networkCapabilities)
                             && !checkConnectivityRestrictedNetworksPermission(
                                     nri.mPid, nri.mUid)) {
                         requestToBeReleased = req;
@@ -6140,13 +6148,6 @@
         }
     }
 
-    private void ensureRequestableCapabilities(NetworkCapabilities networkCapabilities) {
-        final String badCapability = networkCapabilities.describeFirstNonRequestableCapability();
-        if (badCapability != null) {
-            throw new IllegalArgumentException("Cannot request network with " + badCapability);
-        }
-    }
-
     // This checks that the passed capabilities either do not request a
     // specific SSID/SignalStrength, or the calling app has permission to do so.
     private void ensureSufficientPermissionsForRequest(NetworkCapabilities nc,
@@ -6204,7 +6205,7 @@
         nai.onSignalStrengthThresholdsUpdated(thresholdsArray);
     }
 
-    private void ensureValidNetworkSpecifier(NetworkCapabilities nc) {
+    private static void ensureValidNetworkSpecifier(NetworkCapabilities nc) {
         if (nc == null) {
             return;
         }
@@ -6217,7 +6218,7 @@
         }
     }
 
-    private void ensureValid(NetworkCapabilities nc) {
+    private static void ensureListenableCapabilities(@NonNull final NetworkCapabilities nc) {
         ensureValidNetworkSpecifier(nc);
         if (nc.isPrivateDnsBroken()) {
             throw new IllegalArgumentException("Can't request broken private DNS");
@@ -6227,6 +6228,14 @@
         }
     }
 
+    private void ensureRequestableCapabilities(@NonNull final NetworkCapabilities nc) {
+        ensureListenableCapabilities(nc);
+        final String badCapability = nc.describeFirstNonRequestableCapability();
+        if (badCapability != null) {
+            throw new IllegalArgumentException("Cannot request network with " + badCapability);
+        }
+    }
+
     // TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
     @TargetApi(Build.VERSION_CODES.S)
     private boolean isTargetSdkAtleast(int version, int callingUid,
@@ -6319,7 +6328,6 @@
         if (timeoutMs < 0) {
             throw new IllegalArgumentException("Bad timeout specified");
         }
-        ensureValid(networkCapabilities);
 
         final NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
                 nextNetworkRequestId(), reqType);
@@ -6461,7 +6469,6 @@
                 Binder.getCallingPid(), callingUid, callingPackageName);
         restrictRequestUidsForCallerAndSetRequestorInfo(networkCapabilities,
                 callingUid, callingPackageName);
-        ensureValid(networkCapabilities);
 
         NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
                 nextNetworkRequestId(), NetworkRequest.Type.REQUEST);
@@ -6528,7 +6535,7 @@
         // There is no need to do this for requests because an app without CHANGE_NETWORK_STATE
         // can't request networks.
         restrictBackgroundRequestForCaller(nc);
-        ensureValid(nc);
+        ensureListenableCapabilities(nc);
 
         NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
                 NetworkRequest.Type.LISTEN);
@@ -6550,7 +6557,7 @@
         if (!hasWifiNetworkListenPermission(networkCapabilities)) {
             enforceAccessPermission();
         }
-        ensureValid(networkCapabilities);
+        ensureListenableCapabilities(networkCapabilities);
         ensureSufficientPermissionsForRequest(networkCapabilities,
                 Binder.getCallingPid(), callingUid, callingPackageName);
         final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
@@ -7495,7 +7502,8 @@
             nc.setOwnerUid(nai.networkCapabilities.getOwnerUid());
         }
         nai.declaredCapabilities = new NetworkCapabilities(nc);
-        NetworkAgentInfo.restrictCapabilitiesFromNetworkAgent(nc, nai.creatorUid);
+        NetworkAgentInfo.restrictCapabilitiesFromNetworkAgent(nc, nai.creatorUid,
+                mCarrierPrivilegeAuthenticator);
     }
 
     /** Modifies |newNc| based on the capabilities of |underlyingNetworks| and |agentCaps|. */
@@ -10843,7 +10851,7 @@
             } else {
                 mBpfNetMaps.removeNiceApp(uid);
             }
-        } catch (ServiceSpecificException e) {
+        } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
     }
@@ -10858,7 +10866,7 @@
             } else {
                 mBpfNetMaps.removeNaughtyApp(uid);
             }
-        } catch (ServiceSpecificException e) {
+        } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
     }
@@ -10870,7 +10878,7 @@
         try {
             mBpfNetMaps.setUidRule(chain, uid,
                     allow ? INetd.FIREWALL_RULE_ALLOW : INetd.FIREWALL_RULE_DENY);
-        } catch (ServiceSpecificException e) {
+        } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
     }
@@ -10881,7 +10889,7 @@
 
         try {
             mBpfNetMaps.setChildChain(chain, enable);
-        } catch (ServiceSpecificException e) {
+        } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
     }
@@ -10908,7 +10916,7 @@
                     throw new IllegalArgumentException("replaceFirewallChain with invalid chain: "
                             + chain);
             }
-        } catch (ServiceSpecificException e) {
+        } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
     }
@@ -10918,7 +10926,7 @@
         enforceNetworkStackOrSettingsPermission();
         try {
             mBpfNetMaps.swapActiveStatsMap();
-        } catch (ServiceSpecificException e) {
+        } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
     }
diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
index c5107ad..ce955fd 100644
--- a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
+++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
@@ -17,6 +17,7 @@
 package com.android.server.connectivity;
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 
 import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
@@ -25,7 +26,7 @@
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.net.NetworkRequest;
+import android.net.NetworkCapabilities;
 import android.net.NetworkSpecifier;
 import android.net.TelephonyNetworkSpecifier;
 import android.os.Build;
@@ -235,24 +236,30 @@
     }
 
     /**
-     * Check if network request is allowed based upon carrrier service package.
+     * Check if a UID is the carrier service app of the subscription ID in the provided capabilities
      *
-     * Network request for {@link NET_CAPABILITY_CBS} is allowed if the caller has
-     * carrier privilege and provides the carrier config. This function checks if caller
-     * has the same and returns true if it has else false.
+     * This returns whether the passed UID is the carrier service package for the subscription ID
+     * stored in the telephony network specifier in the passed network capabilities.
+     * If the capabilities don't code for a cellular network, or if they don't have the
+     * subscription ID in their specifier, this returns false.
      *
-     * @param callingUid user identifier that uniquely identifies the caller.
-     * @param networkRequest the network request for which the carrier privilege is checked.
-     * @return true if caller has carrier privilege and provides the carrier config else false.
+     * This method can be used to check that a network request for {@link NET_CAPABILITY_CBS} is
+     * allowed for the UID of a caller, which must hold carrier privilege and provide the carrier
+     * config.
+     * It can also be used to check that a factory is entitled to grant access to a given network
+     * to a given UID on grounds that it is the carrier service package.
+     *
+     * @param callingUid uid of the app claimed to be the carrier service package.
+     * @param networkCapabilities the network capabilities for which carrier privilege is checked.
+     * @return true if uid provides the relevant carrier config else false.
      */
-    public boolean hasCarrierPrivilegeForNetworkRequest(int callingUid,
-            NetworkRequest networkRequest) {
-        if (callingUid != Process.INVALID_UID) {
-            final int subId = getSubIdFromNetworkSpecifier(
-                    networkRequest.getNetworkSpecifier());
-            return callingUid == getCarrierServiceUidForSubId(subId);
-        }
-        return false;
+    public boolean hasCarrierPrivilegeForNetworkCapabilities(int callingUid,
+            @NonNull NetworkCapabilities networkCapabilities) {
+        if (callingUid == Process.INVALID_UID) return false;
+        if (!networkCapabilities.hasSingleTransport(TRANSPORT_CELLULAR)) return false;
+        final int subId = getSubIdFromNetworkSpecifier(networkCapabilities.getNetworkSpecifier());
+        if (SubscriptionManager.INVALID_SUBSCRIPTION_ID == subId) return false;
+        return callingUid == getCarrierServiceUidForSubId(subId);
     }
 
     @VisibleForTesting
@@ -286,7 +293,7 @@
         if (specifier instanceof TelephonyNetworkSpecifier) {
             return ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
         }
-        return SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+        return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     }
 
     @VisibleForTesting
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index b9e2a5f..e917b3f 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -60,6 +60,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.util.WakeupMessage;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.ConnectivityService;
 
 import java.io.PrintWriter;
@@ -1196,21 +1197,26 @@
      *
      * @param nc the capabilities to sanitize
      * @param creatorUid the UID of the process creating this network agent
+     * @param authenticator the carrier privilege authenticator to check for telephony constraints
      */
     public static void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc,
-            final int creatorUid) {
+            final int creatorUid, @NonNull final CarrierPrivilegeAuthenticator authenticator) {
         if (nc.hasTransport(TRANSPORT_TEST)) {
             nc.restrictCapabilitiesForTestNetwork(creatorUid);
         }
-        if (!areAccessUidsAcceptableFromNetworkAgent(nc)) {
+        if (!areAccessUidsAcceptableFromNetworkAgent(nc, authenticator)) {
             nc.setAccessUids(new ArraySet<>());
         }
     }
 
     private static boolean areAccessUidsAcceptableFromNetworkAgent(
-            @NonNull final NetworkCapabilities nc) {
+            @NonNull final NetworkCapabilities nc,
+            @Nullable final CarrierPrivilegeAuthenticator carrierPrivilegeAuthenticator) {
         // NCs without access UIDs are fine.
         if (!nc.hasAccessUids()) return true;
+        // S and below must never accept access UIDs, even if an agent sends them, because netd
+        // didn't support the required feature in S.
+        if (!SdkLevel.isAtLeastT()) return false;
 
         // On a non-restricted network, access UIDs make no sense
         if (nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) return false;
@@ -1219,7 +1225,18 @@
         // access UIDs
         if (nc.hasTransport(TRANSPORT_TEST)) return true;
 
-        // TODO : accept more supported cases
+        // Factories that make cell networks can allow the UID for the carrier service package.
+        // This can only work in T where there is support for CarrierPrivilegeAuthenticator
+        if (null != carrierPrivilegeAuthenticator
+                && nc.hasSingleTransport(TRANSPORT_CELLULAR)
+                && (1 == nc.getAccessUidsNoCopy().size())
+                && (carrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                        nc.getAccessUidsNoCopy().valueAt(0), nc))) {
+            return true;
+        }
+
+        // TODO : accept Railway callers
+
         return false;
     }
 
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index c9c1776..ac46054 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -58,7 +58,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.system.OsConstants;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -812,12 +811,8 @@
             } else {
                 mBpfNetMaps.removeUidInterfaceRules(toIntArray(uids));
             }
-        } catch (ServiceSpecificException e) {
-            // Silently ignore exception when device does not support eBPF, otherwise just log
-            // the exception and do not crash
-            if (e.errorCode != OsConstants.EOPNOTSUPP) {
-                loge("Exception when updating permissions: ", e);
-            }
+        } catch (RemoteException | ServiceSpecificException e) {
+            loge("Exception when updating permissions: ", e);
         }
     }
 
@@ -901,7 +896,7 @@
                 mBpfNetMaps.setNetPermForUids(PERMISSION_UNINSTALLED,
                         toIntArray(uninstalledAppIds));
             }
-        } catch (ServiceSpecificException e) {
+        } catch (RemoteException | ServiceSpecificException e) {
             Log.e(TAG, "Pass appId list of special permission failed." + e);
         }
     }
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityFrameworkInitializerTiramisuTest.kt b/tests/cts/net/src/android/net/cts/ConnectivityFrameworkInitializerTiramisuTest.kt
new file mode 100644
index 0000000..049372f
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/ConnectivityFrameworkInitializerTiramisuTest.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.cts
+
+import android.net.nsd.NsdManager
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.networkstack.apishim.ConnectivityFrameworkInitShimImpl
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.SC_V2
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertNotNull
+
+private val cfiShim = ConnectivityFrameworkInitShimImpl.newInstance()
+
+@RunWith(DevSdkIgnoreRunner::class)
+// ConnectivityFrameworkInitializerTiramisu was added in T
+@IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+class ConnectivityFrameworkInitializerTiramisuTest {
+    @Test
+    fun testServicesRegistered() {
+        val ctx = InstrumentationRegistry.getInstrumentation().context as android.content.Context
+        assertNotNull(ctx.getSystemService(NsdManager::class.java),
+                "NsdManager not registered")
+    }
+
+    // registerServiceWrappers can only be called during initialization and should throw otherwise
+    @Test(expected = IllegalStateException::class)
+    fun testThrows() {
+        cfiShim.registerServiceWrappers()
+    }
+}
diff --git a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index 1f39fee..886b078 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -146,8 +146,9 @@
     fun tearDown() {
         agentsToCleanUp.forEach { it.unregister() }
         callbacksToCleanUp.forEach { cm.unregisterNetworkCallback(it) }
+
+        // reader.stop() cleans up tun fd
         reader.handler.post { reader.stop() }
-        Os.close(iface.fileDescriptor.fileDescriptor)
         handlerThread.quitSafely()
     }
 
@@ -519,4 +520,4 @@
 
 private fun <T> Context.assertHasService(manager: Class<T>): T {
     return getSystemService(manager) ?: fail("Service $manager not found")
-}
\ No newline at end of file
+}
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
new file mode 100644
index 0000000..147fca9
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -0,0 +1,878 @@
+/*
+ * Copyright (C) 2015 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.cts;
+
+import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_ALL;
+import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_NO;
+import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_YES;
+import static android.app.usage.NetworkStats.Bucket.METERED_ALL;
+import static android.app.usage.NetworkStats.Bucket.METERED_NO;
+import static android.app.usage.NetworkStats.Bucket.METERED_YES;
+import static android.app.usage.NetworkStats.Bucket.STATE_ALL;
+import static android.app.usage.NetworkStats.Bucket.STATE_DEFAULT;
+import static android.app.usage.NetworkStats.Bucket.STATE_FOREGROUND;
+import static android.app.usage.NetworkStats.Bucket.TAG_NONE;
+import static android.app.usage.NetworkStats.Bucket.UID_ALL;
+
+import android.app.AppOpsManager;
+import android.app.usage.NetworkStats;
+import android.app.usage.NetworkStatsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.net.TrafficStats;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.telephony.TelephonyManager;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+
+public class NetworkStatsManagerTest extends InstrumentationTestCase {
+    private static final String LOG_TAG = "NetworkStatsManagerTest";
+    private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} {1} {2}";
+    private static final String APPOPS_GET_SHELL_COMMAND = "appops get {0} {1}";
+
+    private static final long MINUTE = 1000 * 60;
+    private static final int TIMEOUT_MILLIS = 15000;
+
+    private static final String CHECK_CONNECTIVITY_URL = "http://www.265.com/";
+    private static final int HOST_RESOLUTION_RETRIES = 4;
+    private static final int HOST_RESOLUTION_INTERVAL_MS = 500;
+
+    private static final int NETWORK_TAG = 0xf00d;
+    private static final long THRESHOLD_BYTES = 2 * 1024 * 1024;  // 2 MB
+
+    private abstract class NetworkInterfaceToTest {
+        private boolean mMetered;
+        private boolean mIsDefault;
+
+        abstract int getNetworkType();
+        abstract int getTransportType();
+
+        public boolean getMetered() {
+            return mMetered;
+        }
+
+        public void setMetered(boolean metered) {
+            this.mMetered = metered;
+        }
+
+        public boolean getIsDefault() {
+            return mIsDefault;
+        }
+
+        public void setIsDefault(boolean isDefault) {
+            mIsDefault = isDefault;
+        }
+
+        abstract String getSystemFeature();
+        abstract String getErrorMessage();
+    }
+
+    private final NetworkInterfaceToTest[] mNetworkInterfacesToTest =
+            new NetworkInterfaceToTest[] {
+                    new NetworkInterfaceToTest() {
+                        @Override
+                        public int getNetworkType() {
+                            return ConnectivityManager.TYPE_WIFI;
+                        }
+
+                        @Override
+                        public int getTransportType() {
+                            return NetworkCapabilities.TRANSPORT_WIFI;
+                        }
+
+                        @Override
+                        public String getSystemFeature() {
+                            return PackageManager.FEATURE_WIFI;
+                        }
+
+                        @Override
+                        public String getErrorMessage() {
+                            return " Please make sure you are connected to a WiFi access point.";
+                        }
+                    },
+                    new NetworkInterfaceToTest() {
+                        @Override
+                        public int getNetworkType() {
+                            return ConnectivityManager.TYPE_MOBILE;
+                        }
+
+                        @Override
+                        public int getTransportType() {
+                            return NetworkCapabilities.TRANSPORT_CELLULAR;
+                        }
+
+                        @Override
+                        public String getSystemFeature() {
+                            return PackageManager.FEATURE_TELEPHONY;
+                        }
+
+                        @Override
+                        public String getErrorMessage() {
+                            return " Please make sure you have added a SIM card with data plan to"
+                                    + " your phone, have enabled data over cellular and in case of"
+                                    + " dual SIM devices, have selected the right SIM "
+                                    + "for data connection.";
+                        }
+                    }
+            };
+
+    private String mPkg;
+    private NetworkStatsManager mNsm;
+    private ConnectivityManager mCm;
+    private PackageManager mPm;
+    private long mStartTime;
+    private long mEndTime;
+
+    private long mBytesRead;
+    private String mWriteSettingsMode;
+    private String mUsageStatsMode;
+
+    private void exerciseRemoteHost(Network network, URL url) throws Exception {
+        NetworkInfo networkInfo = mCm.getNetworkInfo(network);
+        if (networkInfo == null) {
+            Log.w(LOG_TAG, "Network info is null");
+        } else {
+            Log.w(LOG_TAG, "Network: " + networkInfo.toString());
+        }
+        InputStreamReader in = null;
+        HttpURLConnection urlc = null;
+        String originalKeepAlive = System.getProperty("http.keepAlive");
+        System.setProperty("http.keepAlive", "false");
+        try {
+            TrafficStats.setThreadStatsTag(NETWORK_TAG);
+            urlc = (HttpURLConnection) network.openConnection(url);
+            urlc.setConnectTimeout(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.
+            urlc.setRequestProperty("Accept-Encoding", "identity");
+            urlc.connect();
+            boolean ping = urlc.getResponseCode() == 200;
+            if (ping) {
+                in = new InputStreamReader(
+                        (InputStream) urlc.getContent());
+
+                mBytesRead = 0;
+                while (in.read() != -1) ++mBytesRead;
+            }
+        } catch (Exception e) {
+            Log.i(LOG_TAG, "Badness during exercising remote server: " + e);
+        } finally {
+            TrafficStats.clearThreadStatsTag();
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    // don't care
+                }
+            }
+            if (urlc != null) {
+                urlc.disconnect();
+            }
+            if (originalKeepAlive == null) {
+                System.clearProperty("http.keepAlive");
+            } else {
+                System.setProperty("http.keepAlive", originalKeepAlive);
+            }
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mNsm = (NetworkStatsManager) getInstrumentation().getContext()
+                .getSystemService(Context.NETWORK_STATS_SERVICE);
+        mNsm.setPollForce(true);
+
+        mCm = (ConnectivityManager) getInstrumentation().getContext()
+                .getSystemService(Context.CONNECTIVITY_SERVICE);
+
+        mPm = getInstrumentation().getContext().getPackageManager();
+
+        mPkg = getInstrumentation().getContext().getPackageName();
+
+        mWriteSettingsMode = getAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS);
+        setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, "allow");
+        mUsageStatsMode = getAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mWriteSettingsMode != null) {
+            setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, mWriteSettingsMode);
+        }
+        if (mUsageStatsMode != null) {
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, mUsageStatsMode);
+        }
+        super.tearDown();
+    }
+
+    private void setAppOpsMode(String appop, String mode) throws Exception {
+        final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND, mPkg, appop, mode);
+        SystemUtil.runShellCommand(command);
+    }
+
+    private String getAppOpsMode(String appop) throws Exception {
+        final String command = MessageFormat.format(APPOPS_GET_SHELL_COMMAND, mPkg, appop);
+        String result = SystemUtil.runShellCommand(command);
+        if (result == null) {
+            Log.w(LOG_TAG, "App op " + appop + " could not be read.");
+        }
+        return result;
+    }
+
+    private boolean isInForeground() throws IOException {
+        String result = SystemUtil.runShellCommand(getInstrumentation(),
+                "cmd activity get-uid-state " + Process.myUid());
+        return result.contains("FOREGROUND");
+    }
+
+    private class NetworkCallback extends ConnectivityManager.NetworkCallback {
+        private long mTolerance;
+        private URL mUrl;
+        public boolean success;
+        public boolean metered;
+        public boolean isDefault;
+
+        NetworkCallback(long tolerance, URL url) {
+            mTolerance = tolerance;
+            mUrl = url;
+            success = false;
+            metered = false;
+            isDefault = false;
+        }
+
+        // The test host only has IPv4. So on a dual-stack network where IPv6 connects before IPv4,
+        // we need to wait until IPv4 is available or the test will spuriously fail.
+        private void waitForHostResolution(Network network) {
+            for (int i = 0; i < HOST_RESOLUTION_RETRIES; i++) {
+                try {
+                    network.getAllByName(mUrl.getHost());
+                    return;
+                } catch (UnknownHostException e) {
+                    SystemClock.sleep(HOST_RESOLUTION_INTERVAL_MS);
+                }
+            }
+            fail(String.format("%s could not be resolved on network %s (%d attempts %dms apart)",
+                    mUrl.getHost(), network, HOST_RESOLUTION_RETRIES, HOST_RESOLUTION_INTERVAL_MS));
+        }
+
+        @Override
+        public void onAvailable(Network network) {
+            try {
+                mStartTime = System.currentTimeMillis() - mTolerance;
+                isDefault = network.equals(mCm.getActiveNetwork());
+                waitForHostResolution(network);
+                exerciseRemoteHost(network, mUrl);
+                mEndTime = System.currentTimeMillis() + mTolerance;
+                success = true;
+                metered = !mCm.getNetworkCapabilities(network)
+                        .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+                synchronized (NetworkStatsManagerTest.this) {
+                    NetworkStatsManagerTest.this.notify();
+                }
+            } catch (Exception e) {
+                Log.w(LOG_TAG, "exercising remote host failed.", e);
+                success = false;
+            }
+        }
+    }
+
+    private boolean shouldTestThisNetworkType(int networkTypeIndex, final long tolerance)
+            throws Exception {
+        boolean hasFeature = mPm.hasSystemFeature(
+                mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature());
+        if (!hasFeature) {
+            return false;
+        }
+        NetworkCallback callback = new NetworkCallback(tolerance, new URL(CHECK_CONNECTIVITY_URL));
+        mCm.requestNetwork(new NetworkRequest.Builder()
+                .addTransportType(mNetworkInterfacesToTest[networkTypeIndex].getTransportType())
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .build(), callback);
+        synchronized (this) {
+            try {
+                wait((int) (TIMEOUT_MILLIS * 1.2));
+            } catch (InterruptedException e) {
+            }
+        }
+        if (callback.success) {
+            mNetworkInterfacesToTest[networkTypeIndex].setMetered(callback.metered);
+            mNetworkInterfacesToTest[networkTypeIndex].setIsDefault(callback.isDefault);
+            return true;
+        }
+
+        // This will always fail at this point as we know 'hasFeature' is true.
+        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)."
+                + mNetworkInterfacesToTest[networkTypeIndex].getErrorMessage(), hasFeature);
+        return false;
+    }
+
+    private String getSubscriberId(int networkIndex) {
+        int networkType = mNetworkInterfacesToTest[networkIndex].getNetworkType();
+        if (ConnectivityManager.TYPE_MOBILE == networkType) {
+            TelephonyManager tm = (TelephonyManager) getInstrumentation().getContext()
+                    .getSystemService(Context.TELEPHONY_SERVICE);
+            return ShellIdentityUtils.invokeMethodWithShellPermissions(tm,
+                    (telephonyManager) -> telephonyManager.getSubscriberId());
+        }
+        return "";
+    }
+
+    @AppModeFull
+    public void testDeviceSummary() throws Exception {
+        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+            if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
+                continue;
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+            NetworkStats.Bucket bucket = null;
+            try {
+                bucket = mNsm.querySummaryForDevice(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime);
+            } catch (RemoteException | SecurityException e) {
+                fail("testDeviceSummary fails with exception: " + e.toString());
+            }
+            assertNotNull(bucket);
+            assertTimestamps(bucket);
+            assertEquals(bucket.getState(), STATE_ALL);
+            assertEquals(bucket.getUid(), UID_ALL);
+            assertEquals(bucket.getMetered(), METERED_ALL);
+            assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+            try {
+                bucket = mNsm.querySummaryForDevice(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime);
+                fail("negative testDeviceSummary fails: no exception thrown.");
+            } catch (RemoteException e) {
+                fail("testDeviceSummary fails with exception: " + e.toString());
+            } catch (SecurityException e) {
+                // expected outcome
+            }
+        }
+    }
+
+    @AppModeFull
+    public void testUserSummary() throws Exception {
+        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+            if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
+                continue;
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+            NetworkStats.Bucket bucket = null;
+            try {
+                bucket = mNsm.querySummaryForUser(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime);
+            } catch (RemoteException | SecurityException e) {
+                fail("testUserSummary fails with exception: " + e.toString());
+            }
+            assertNotNull(bucket);
+            assertTimestamps(bucket);
+            assertEquals(bucket.getState(), STATE_ALL);
+            assertEquals(bucket.getUid(), UID_ALL);
+            assertEquals(bucket.getMetered(), METERED_ALL);
+            assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+            try {
+                bucket = mNsm.querySummaryForUser(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime);
+                fail("negative testUserSummary fails: no exception thrown.");
+            } catch (RemoteException e) {
+                fail("testUserSummary fails with exception: " + e.toString());
+            } catch (SecurityException e) {
+                // expected outcome
+            }
+        }
+    }
+
+    @AppModeFull
+    public void testAppSummary() throws Exception {
+        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+            // Use tolerance value that large enough to make sure stats of at
+            // least one bucket is included. However, this is possible that
+            // the test will see data of different app but with the same UID
+            // that created before testing.
+            // TODO: Consider query stats before testing and use the difference to verify.
+            if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+                continue;
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+            NetworkStats result = null;
+            try {
+                result = mNsm.querySummary(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime);
+                assertNotNull(result);
+                NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+                long totalTxPackets = 0;
+                long totalRxPackets = 0;
+                long totalTxBytes = 0;
+                long totalRxBytes = 0;
+                boolean hasCorrectMetering = false;
+                boolean hasCorrectDefaultStatus = false;
+                int expectedMetering = mNetworkInterfacesToTest[i].getMetered()
+                        ? METERED_YES : METERED_NO;
+                int expectedDefaultStatus = mNetworkInterfacesToTest[i].getIsDefault()
+                        ? DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO;
+                while (result.hasNextBucket()) {
+                    assertTrue(result.getNextBucket(bucket));
+                    assertTimestamps(bucket);
+                    hasCorrectMetering |= bucket.getMetered() == expectedMetering;
+                    if (bucket.getUid() == Process.myUid()) {
+                        totalTxPackets += bucket.getTxPackets();
+                        totalRxPackets += bucket.getRxPackets();
+                        totalTxBytes += bucket.getTxBytes();
+                        totalRxBytes += bucket.getRxBytes();
+                        hasCorrectDefaultStatus |=
+                                bucket.getDefaultNetworkStatus() == expectedDefaultStatus;
+                    }
+                }
+                assertFalse(result.getNextBucket(bucket));
+                assertTrue("Incorrect metering for NetworkType: "
+                        + mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectMetering);
+                assertTrue("Incorrect isDefault for NetworkType: "
+                        + mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectDefaultStatus);
+                assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0);
+                assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0);
+                assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0);
+                assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0);
+            } finally {
+                if (result != null) {
+                    result.close();
+                }
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+            try {
+                result = mNsm.querySummary(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime);
+                fail("negative testAppSummary fails: no exception thrown.");
+            } catch (RemoteException e) {
+                fail("testAppSummary fails with exception: " + e.toString());
+            } catch (SecurityException e) {
+                // expected outcome
+            }
+        }
+    }
+
+    @AppModeFull
+    public void testAppDetails() throws Exception {
+        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+            // Relatively large tolerance to accommodate for history bucket size.
+            if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+                continue;
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+            NetworkStats result = null;
+            try {
+                result = mNsm.queryDetails(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime);
+                long totalBytesWithSubscriberId = getTotalAndAssertNotEmpty(result);
+
+                // Test without filtering by subscriberId
+                result = mNsm.queryDetails(
+                        mNetworkInterfacesToTest[i].getNetworkType(), null,
+                        mStartTime, mEndTime);
+
+                assertTrue("More bytes with subscriberId filter than without.",
+                        getTotalAndAssertNotEmpty(result) >= totalBytesWithSubscriberId);
+            } catch (RemoteException | SecurityException e) {
+                fail("testAppDetails fails with exception: " + e.toString());
+            } finally {
+                if (result != null) {
+                    result.close();
+                }
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+            try {
+                result = mNsm.queryDetails(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime);
+                fail("negative testAppDetails fails: no exception thrown.");
+            } catch (RemoteException e) {
+                fail("testAppDetails fails with exception: " + e.toString());
+            } catch (SecurityException e) {
+                // expected outcome
+            }
+        }
+    }
+
+    @AppModeFull
+    public void testUidDetails() throws Exception {
+        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+            // Relatively large tolerance to accommodate for history bucket size.
+            if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+                continue;
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+            NetworkStats result = null;
+            try {
+                result = mNsm.queryDetailsForUid(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime, Process.myUid());
+                assertNotNull(result);
+                NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+                long totalTxPackets = 0;
+                long totalRxPackets = 0;
+                long totalTxBytes = 0;
+                long totalRxBytes = 0;
+                while (result.hasNextBucket()) {
+                    assertTrue(result.getNextBucket(bucket));
+                    assertTimestamps(bucket);
+                    assertEquals(bucket.getState(), STATE_ALL);
+                    assertEquals(bucket.getMetered(), METERED_ALL);
+                    assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+                    assertEquals(bucket.getUid(), Process.myUid());
+                    totalTxPackets += bucket.getTxPackets();
+                    totalRxPackets += bucket.getRxPackets();
+                    totalTxBytes += bucket.getTxBytes();
+                    totalRxBytes += bucket.getRxBytes();
+                }
+                assertFalse(result.getNextBucket(bucket));
+                assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0);
+                assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0);
+                assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0);
+                assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0);
+            } finally {
+                if (result != null) {
+                    result.close();
+                }
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+            try {
+                result = mNsm.queryDetailsForUid(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime, Process.myUid());
+                fail("negative testUidDetails fails: no exception thrown.");
+            } catch (SecurityException e) {
+                // expected outcome
+            }
+        }
+    }
+
+    @AppModeFull
+    public void testTagDetails() throws Exception {
+        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+            // Relatively large tolerance to accommodate for history bucket size.
+            if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+                continue;
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+            NetworkStats result = null;
+            try {
+                result = mNsm.queryDetailsForUidTag(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
+                assertNotNull(result);
+                NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+                long totalTxPackets = 0;
+                long totalRxPackets = 0;
+                long totalTxBytes = 0;
+                long totalRxBytes = 0;
+                while (result.hasNextBucket()) {
+                    assertTrue(result.getNextBucket(bucket));
+                    assertTimestamps(bucket);
+                    assertEquals(bucket.getState(), STATE_ALL);
+                    assertEquals(bucket.getMetered(), METERED_ALL);
+                    assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+                    assertEquals(bucket.getUid(), Process.myUid());
+                    if (bucket.getTag() == NETWORK_TAG) {
+                        totalTxPackets += bucket.getTxPackets();
+                        totalRxPackets += bucket.getRxPackets();
+                        totalTxBytes += bucket.getTxBytes();
+                        totalRxBytes += bucket.getRxBytes();
+                    }
+                }
+                assertTrue("No Rx bytes tagged with 0x" + Integer.toHexString(NETWORK_TAG)
+                        + " for uid " + Process.myUid(), totalRxBytes > 0);
+                assertTrue("No Rx packets tagged with 0x" + Integer.toHexString(NETWORK_TAG)
+                        + " for uid " + Process.myUid(), totalRxPackets > 0);
+                assertTrue("No Tx bytes tagged with 0x" + Integer.toHexString(NETWORK_TAG)
+                        + " for uid " + Process.myUid(), totalTxBytes > 0);
+                assertTrue("No Tx packets tagged with 0x" + Integer.toHexString(NETWORK_TAG)
+                        + " for uid " + Process.myUid(), totalTxPackets > 0);
+            } finally {
+                if (result != null) {
+                    result.close();
+                }
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+            try {
+                result = mNsm.queryDetailsForUidTag(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
+                fail("negative testUidDetails fails: no exception thrown.");
+            } catch (SecurityException e) {
+                // expected outcome
+            }
+        }
+    }
+
+    class QueryResult {
+        public final int tag;
+        public final int state;
+        public final long total;
+
+        QueryResult(int tag, int state, NetworkStats stats) {
+            this.tag = tag;
+            this.state = state;
+            total = getTotalAndAssertNotEmpty(stats, tag, state);
+        }
+
+        public String toString() {
+            return String.format("QueryResult(tag=%s state=%s total=%d)",
+                    tagToString(tag), stateToString(state), total);
+        }
+    }
+
+    private NetworkStats getNetworkStatsForTagState(int i, int tag, int state) {
+        return mNsm.queryDetailsForUidTagState(
+                mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                mStartTime, mEndTime, Process.myUid(), tag, state);
+    }
+
+    private void assertWithinPercentage(String msg, long expected, long actual, int percentage) {
+        long lowerBound = expected * (100 - percentage) / 100;
+        long upperBound = expected * (100 + percentage) / 100;
+        msg = String.format("%s: %d not within %d%% of %d", msg, actual, percentage, expected);
+        assertTrue(msg, lowerBound <= actual);
+        assertTrue(msg, upperBound >= actual);
+    }
+
+    private void assertAlmostNoUnexpectedTraffic(NetworkStats result, int expectedTag,
+            int expectedState, long maxUnexpected) {
+        long total = 0;
+        NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+        while (result.hasNextBucket()) {
+            assertTrue(result.getNextBucket(bucket));
+            total += bucket.getRxBytes() + bucket.getTxBytes();
+        }
+        if (total <= maxUnexpected) return;
+
+        fail(String.format("More than %d bytes of traffic when querying for "
+                + "tag %s state %s. Last bucket: uid=%d tag=%s state=%s bytes=%d/%d",
+                maxUnexpected, tagToString(expectedTag), stateToString(expectedState),
+                bucket.getUid(), tagToString(bucket.getTag()), stateToString(bucket.getState()),
+                bucket.getRxBytes(), bucket.getTxBytes()));
+    }
+
+    @AppModeFull
+    public void testUidTagStateDetails() throws Exception {
+        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+            // Relatively large tolerance to accommodate for history bucket size.
+            if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
+                continue;
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+            NetworkStats result = null;
+            try {
+                int currentState = isInForeground() ? STATE_FOREGROUND : STATE_DEFAULT;
+                int otherState = (currentState == STATE_DEFAULT) ? STATE_FOREGROUND : STATE_DEFAULT;
+
+                int[] tagsWithTraffic = {NETWORK_TAG, TAG_NONE};
+                int[] statesWithTraffic = {currentState, STATE_ALL};
+                ArrayList<QueryResult> resultsWithTraffic = new ArrayList<>();
+
+                int[] statesWithNoTraffic = {otherState};
+                int[] tagsWithNoTraffic = {NETWORK_TAG + 1};
+                ArrayList<QueryResult> resultsWithNoTraffic = new ArrayList<>();
+
+                // Expect to see traffic when querying for any combination of a tag in
+                // tagsWithTraffic and a state in statesWithTraffic.
+                for (int tag : tagsWithTraffic) {
+                    for (int state : statesWithTraffic) {
+                        result = getNetworkStatsForTagState(i, tag, state);
+                        resultsWithTraffic.add(new QueryResult(tag, state, result));
+                        result.close();
+                        result = null;
+                    }
+                }
+
+                // Expect that the results are within a few percentage points of each other.
+                // This is ensures that FIN retransmits after the transfer is complete don't cause
+                // the test to be flaky. The test URL currently returns just over 100k so this
+                // should not be too noisy. It also ensures that the traffic sent by the test
+                // 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);
+                }
+
+                // Expect to see no traffic when querying for any tag in tagsWithNoTraffic or any
+                // state in statesWithNoTraffic.
+                for (int tag : tagsWithNoTraffic) {
+                    for (int state : statesWithTraffic) {
+                        result = getNetworkStatsForTagState(i, tag, state);
+                        assertAlmostNoUnexpectedTraffic(result, tag, state, firstTotal / 100);
+                        result.close();
+                        result = null;
+                    }
+                }
+                for (int tag : tagsWithTraffic) {
+                    for (int state : statesWithNoTraffic) {
+                        result = getNetworkStatsForTagState(i, tag, state);
+                        assertAlmostNoUnexpectedTraffic(result, tag, state, firstTotal / 100);
+                        result.close();
+                        result = null;
+                    }
+                }
+            } finally {
+                if (result != null) {
+                    result.close();
+                }
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
+            try {
+                result = mNsm.queryDetailsForUidTag(
+                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
+                        mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
+                fail("negative testUidDetails fails: no exception thrown.");
+            } catch (SecurityException e) {
+                // expected outcome
+            }
+        }
+    }
+
+    @AppModeFull
+    public void testCallback() throws Exception {
+        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
+            // Relatively large tolerance to accommodate for history bucket size.
+            if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
+                continue;
+            }
+            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
+
+            TestUsageCallback usageCallback = new TestUsageCallback();
+            HandlerThread thread = new HandlerThread("callback-thread");
+            thread.start();
+            Handler handler = new Handler(thread.getLooper());
+            mNsm.registerUsageCallback(mNetworkInterfacesToTest[i].getNetworkType(),
+                    getSubscriberId(i), THRESHOLD_BYTES, usageCallback, handler);
+
+            // TODO: Force traffic and check whether the callback is invoked.
+            // Right now the test only covers whether the callback can be registered, but not
+            // whether it is invoked upon data usage since we don't have a scalable way of
+            // storing files of >2MB in CTS.
+
+            mNsm.unregisterUsageCallback(usageCallback);
+        }
+    }
+
+    private String tagToString(Integer tag) {
+        if (tag == null) return "null";
+        switch (tag) {
+            case TAG_NONE:
+                return "TAG_NONE";
+            default:
+                return "0x" + Integer.toHexString(tag);
+        }
+    }
+
+    private String stateToString(Integer state) {
+        if (state == null) return "null";
+        switch (state) {
+            case STATE_ALL:
+                return "STATE_ALL";
+            case STATE_DEFAULT:
+                return "STATE_DEFAULT";
+            case STATE_FOREGROUND:
+                return "STATE_FOREGROUND";
+        }
+        throw new IllegalArgumentException("Unknown state " + state);
+    }
+
+    private long getTotalAndAssertNotEmpty(NetworkStats result, Integer expectedTag,
+            Integer expectedState) {
+        assertTrue(result != null);
+        NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+        long totalTxPackets = 0;
+        long totalRxPackets = 0;
+        long totalTxBytes = 0;
+        long totalRxBytes = 0;
+        while (result.hasNextBucket()) {
+            assertTrue(result.getNextBucket(bucket));
+            assertTimestamps(bucket);
+            if (expectedTag != null) assertEquals(bucket.getTag(), (int) expectedTag);
+            if (expectedState != null) assertEquals(bucket.getState(), (int) expectedState);
+            assertEquals(bucket.getMetered(), METERED_ALL);
+            assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
+            if (bucket.getUid() == Process.myUid()) {
+                totalTxPackets += bucket.getTxPackets();
+                totalRxPackets += bucket.getRxPackets();
+                totalTxBytes += bucket.getTxBytes();
+                totalRxBytes += bucket.getRxBytes();
+            }
+        }
+        assertFalse(result.getNextBucket(bucket));
+        String msg = String.format("uid %d tag %s state %s",
+                Process.myUid(), tagToString(expectedTag), stateToString(expectedState));
+        assertTrue("No Rx bytes usage for " + msg, totalRxBytes > 0);
+        assertTrue("No Rx packets usage for " + msg, totalRxPackets > 0);
+        assertTrue("No Tx bytes usage for " + msg, totalTxBytes > 0);
+        assertTrue("No Tx packets usage for " + msg, totalTxPackets > 0);
+
+        return totalRxBytes + totalTxBytes;
+    }
+
+    private long getTotalAndAssertNotEmpty(NetworkStats result) {
+        return getTotalAndAssertNotEmpty(result, null, STATE_ALL);
+    }
+
+    private void assertTimestamps(final NetworkStats.Bucket bucket) {
+        assertTrue("Start timestamp " + bucket.getStartTimeStamp() + " is less than "
+                + mStartTime, bucket.getStartTimeStamp() >= mStartTime);
+        assertTrue("End timestamp " + bucket.getEndTimeStamp() + " is greater than "
+                + mEndTime, bucket.getEndTimeStamp() <= mEndTime);
+    }
+
+    private static class TestUsageCallback extends NetworkStatsManager.UsageCallback {
+        @Override
+        public void onThresholdReached(int networkType, String subscriberId) {
+            Log.v(LOG_TAG, "Called onThresholdReached for networkType=" + networkType
+                    + " subscriberId=" + subscriberId);
+        }
+    }
+}
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index 4d4e7b9..530fa91 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -53,7 +53,8 @@
         // android_library does not include JNI libs: include NetworkStack dependencies here
         "libnativehelper_compat_libc++",
         "libnetworkstackutilsjni",
-        "libcom_android_connectivity_com_android_net_module_util_jni",
+        "libandroid_net_connectivity_com_android_net_module_util_jni",
+        "libservice-connectivity",
     ],
     jarjar_rules: ":connectivity-jarjar-rules",
 }
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 2985c41..0132525 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -264,6 +264,7 @@
 import android.net.RouteInfo;
 import android.net.RouteInfoParcel;
 import android.net.SocketKeepalive;
+import android.net.TelephonyNetworkSpecifier;
 import android.net.TransportInfo;
 import android.net.UidRange;
 import android.net.UidRangeParcel;
@@ -334,6 +335,7 @@
 import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
 import com.android.server.ConnectivityService.NetworkRequestInfo;
 import com.android.server.ConnectivityServiceTest.ConnectivityServiceDependencies.ReportedInterfaces;
+import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
 import com.android.server.connectivity.ConnectivityFlags;
 import com.android.server.connectivity.MockableSystemProperties;
 import com.android.server.connectivity.Nat464Xlat;
@@ -528,6 +530,8 @@
     @Mock SystemConfigManager mSystemConfigManager;
     @Mock Resources mResources;
     @Mock PacProxyManager mPacProxyManager;
+    @Mock BpfNetMaps mBpfNetMaps;
+    @Mock CarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
 
     // BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the
     // underlying binder calls.
@@ -969,8 +973,6 @@
          * @param hasInternet Indicate if network should pretend to have NET_CAPABILITY_INTERNET.
          */
         public void connect(boolean validated, boolean hasInternet, boolean isStrictMode) {
-            assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_INTERNET));
-
             ConnectivityManager.NetworkCallback callback = null;
             final ConditionVariable validatedCv = new ConditionVariable();
             if (validated) {
@@ -1858,6 +1860,12 @@
         }
 
         @Override
+        public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
+                @NonNull final Context context, @NonNull final TelephonyManager tm) {
+            return SdkLevel.isAtLeastT() ? mCarrierPrivilegeAuthenticator : null;
+        }
+
+        @Override
         public boolean intentFilterEquals(final PendingIntent a, final PendingIntent b) {
             return runAsShell(GET_INTENT_SENDER_INTENT, () -> a.intentFilterEquals(b));
         }
@@ -1950,6 +1958,11 @@
                     return super.isFeatureEnabled(context, name, defaultEnabled);
             }
         }
+
+        @Override
+        public BpfNetMaps getBpfNetMaps(INetd netd) {
+            return mBpfNetMaps;
+        }
     }
 
     private static void initAlarmManager(final AlarmManager am, final Handler alarmHandler) {
@@ -10126,7 +10139,7 @@
         // A connected VPN should have interface rules set up. There are two expected invocations,
         // one during the VPN initial connection, one during the VPN LinkProperties update.
         ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
-        verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+        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.getVpnUidRanges("tun0").equals(vpnRange));
@@ -10135,7 +10148,7 @@
         waitForIdle();
 
         // Disconnected VPN should have interface rules removed
-        verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+        verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
         assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
         assertNull(mService.mPermissionMonitor.getVpnUidRanges("tun0"));
     }
@@ -10152,7 +10165,7 @@
         assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID);
 
         // Legacy VPN should not have interface rules set up
-        verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
+        verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
     }
 
     @Test
@@ -10168,7 +10181,7 @@
         assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID);
 
         // IPv6 unreachable route should not be misinterpreted as a default route
-        verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
+        verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
     }
 
     @Test
@@ -10185,33 +10198,33 @@
         // Connected VPN should have interface rules set up. There are two expected invocations,
         // one during VPN uid update, one during VPN LinkProperties update
         ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
-        verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+        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);
 
-        reset(mMockNetd);
-        InOrder inOrder = inOrder(mMockNetd);
+        reset(mBpfNetMaps);
+        InOrder inOrder = inOrder(mBpfNetMaps);
         lp.setInterfaceName("tun1");
         mMockVpn.sendLinkProperties(lp);
         waitForIdle();
         // VPN handover (switch to a new interface) should result in rules being updated (old rules
         // removed first, then new rules added)
-        inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+        inOrder.verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
         assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
-        inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture());
+        inOrder.verify(mBpfNetMaps).addUidInterfaceRules(eq("tun1"), uidCaptor.capture());
         assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
 
-        reset(mMockNetd);
+        reset(mBpfNetMaps);
         lp = new LinkProperties();
         lp.setInterfaceName("tun1");
         lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun1"));
         mMockVpn.sendLinkProperties(lp);
         waitForIdle();
         // VPN not routing everything should no longer have interface filtering rules
-        verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+        verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
         assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
 
-        reset(mMockNetd);
+        reset(mBpfNetMaps);
         lp = new LinkProperties();
         lp.setInterfaceName("tun1");
         lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
@@ -10219,7 +10232,7 @@
         mMockVpn.sendLinkProperties(lp);
         waitForIdle();
         // Back to routing all IPv6 traffic should have filtering rules
-        verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture());
+        verify(mBpfNetMaps).addUidInterfaceRules(eq("tun1"), uidCaptor.capture());
         assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
     }
 
@@ -10248,8 +10261,8 @@
         mMockVpn.establish(lp, VPN_UID, vpnRanges);
         assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID);
 
-        reset(mMockNetd);
-        InOrder inOrder = inOrder(mMockNetd);
+        reset(mBpfNetMaps);
+        InOrder inOrder = inOrder(mBpfNetMaps);
 
         // Update to new range which is old range minus APP1, i.e. only APP2
         final Set<UidRange> newRanges = new HashSet<>(asList(
@@ -10260,9 +10273,9 @@
 
         ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
         // Verify old rules are removed before new rules are added
-        inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+        inOrder.verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
         assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
-        inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+        inOrder.verify(mBpfNetMaps).addUidInterfaceRules(eq("tun0"), uidCaptor.capture());
         assertContainsExactly(uidCaptor.getValue(), APP2_UID);
     }
 
@@ -14637,43 +14650,157 @@
                 agent.getNetwork().getNetId(),
                 intToUidRangeStableParcels(uids),
                 preferenceOrder);
-        inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids200Parcel);
+        if (SdkLevel.isAtLeastT()) {
+            inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids200Parcel);
+        }
 
         uids.add(300);
         uids.add(400);
         nc.setAccessUids(uids);
         agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
-        cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+        if (SdkLevel.isAtLeastT()) {
+            cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+        } else {
+            cb.assertNoCallback();
+        }
 
         uids.remove(200);
         final NativeUidRangeConfig uids300400Parcel = new NativeUidRangeConfig(
                 agent.getNetwork().getNetId(),
                 intToUidRangeStableParcels(uids),
                 preferenceOrder);
-        inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids300400Parcel);
+        if (SdkLevel.isAtLeastT()) {
+            inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids300400Parcel);
+        }
 
         nc.setAccessUids(uids);
         agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
-        cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
-        inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids200Parcel);
+        if (SdkLevel.isAtLeastT()) {
+            cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+            inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids200Parcel);
+        } else {
+            cb.assertNoCallback();
+        }
 
         uids.clear();
         uids.add(600);
         nc.setAccessUids(uids);
         agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
-        cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+        if (SdkLevel.isAtLeastT()) {
+            cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().equals(uids));
+        } else {
+            cb.assertNoCallback();
+        }
         final NativeUidRangeConfig uids600Parcel = new NativeUidRangeConfig(
                 agent.getNetwork().getNetId(),
                 intToUidRangeStableParcels(uids),
                 preferenceOrder);
-        inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids600Parcel);
-        inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids300400Parcel);
+        if (SdkLevel.isAtLeastT()) {
+            inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids600Parcel);
+            inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids300400Parcel);
+        }
 
         uids.clear();
         nc.setAccessUids(uids);
         agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
-        cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().isEmpty());
-        inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids600Parcel);
+        if (SdkLevel.isAtLeastT()) {
+            cb.expectCapabilitiesThat(agent, caps -> caps.getAccessUids().isEmpty());
+            inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids600Parcel);
+        } else {
+            cb.assertNoCallback();
+            verify(mMockNetd, never()).networkAddUidRangesParcel(any());
+            verify(mMockNetd, never()).networkRemoveUidRangesParcel(any());
+        }
+
+    }
+
+    @Test
+    public void testCbsAccessUids() throws Exception {
+        mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
+        mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
+
+        // In this test TEST_PACKAGE_UID will be the UID of the carrier service UID.
+        doReturn(true).when(mCarrierPrivilegeAuthenticator)
+                .hasCarrierPrivilegeForNetworkCapabilities(eq(TEST_PACKAGE_UID), any());
+
+        final ArraySet<Integer> serviceUidSet = new ArraySet<>();
+        serviceUidSet.add(TEST_PACKAGE_UID);
+        final ArraySet<Integer> nonServiceUidSet = new ArraySet<>();
+        nonServiceUidSet.add(TEST_PACKAGE_UID2);
+        final ArraySet<Integer> serviceUidSetPlus = new ArraySet<>();
+        serviceUidSetPlus.add(TEST_PACKAGE_UID);
+        serviceUidSetPlus.add(TEST_PACKAGE_UID2);
+
+        final TestNetworkCallback cb = new TestNetworkCallback();
+
+        // Simulate a restricted telephony network. The telephony factory is entitled to set
+        // the access UID to the service package on any of its restricted networks.
+        final NetworkCapabilities.Builder ncb = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                .setNetworkSpecifier(new TelephonyNetworkSpecifier(1 /* subid */));
+
+        // Cell gets to set the service UID as access UID
+        mCm.requestNetwork(new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                .build(), cb);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
+                new LinkProperties(), ncb.build());
+        mCellNetworkAgent.connect(true);
+        cb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        ncb.setAccessUids(serviceUidSet);
+        mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+        if (SdkLevel.isAtLeastT()) {
+            cb.expectCapabilitiesThat(mCellNetworkAgent,
+                    caps -> caps.getAccessUids().equals(serviceUidSet));
+        } else {
+            // S must ignore access UIDs.
+            cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
+        }
+
+        // ...but not to some other UID. Rejection sets UIDs to the empty set
+        ncb.setAccessUids(nonServiceUidSet);
+        mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+        if (SdkLevel.isAtLeastT()) {
+            cb.expectCapabilitiesThat(mCellNetworkAgent,
+                    caps -> caps.getAccessUids().isEmpty());
+        } else {
+            // S must ignore access UIDs.
+            cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
+        }
+
+        // ...and also not to multiple UIDs even including the service UID
+        ncb.setAccessUids(serviceUidSetPlus);
+        mCellNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+        cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
+
+        mCellNetworkAgent.disconnect();
+        cb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+        mCm.unregisterNetworkCallback(cb);
+
+        // Must be unset before touching the transports, because remove and add transport types
+        // check the specifier on the builder immediately, contradicting normal builder semantics
+        // TODO : fix the builder
+        ncb.setNetworkSpecifier(null);
+        ncb.removeTransportType(TRANSPORT_CELLULAR);
+        ncb.addTransportType(TRANSPORT_WIFI);
+        // Wifi does not get to set access UID, even to the correct UID
+        mCm.requestNetwork(new NetworkRequest.Builder()
+                .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);
+        ncb.setAccessUids(serviceUidSet);
+        mWiFiNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
+        cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
+        mCm.unregisterNetworkCallback(cb);
     }
 
     /**
diff --git a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
index d193aa9..157507b 100644
--- a/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/CarrierPrivilegeAuthenticatorTest.java
@@ -17,6 +17,7 @@
 package com.android.server.connectivity;
 
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
 
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
@@ -40,7 +41,9 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
 import android.net.TelephonyNetworkSpecifier;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
 import com.android.networkstack.apishim.TelephonyManagerShimImpl;
@@ -66,27 +69,25 @@
 @RunWith(DevSdkIgnoreRunner.class)
 @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
 public class CarrierPrivilegeAuthenticatorTest {
-    private static final String PACKAGE_NAME =
-            CarrierPrivilegeAuthenticatorTest.class.getPackage().getName();
-    private static final int TEST_SIM_SLOT_INDEX = 0;
-    private static final int TEST_SUBSCRIPTION_ID_1 = 2;
-    private static final int TEST_SUBSCRIPTION_ID_2 = 3;
+    private static final int SUBSCRIPTION_COUNT = 2;
+    private static final int TEST_SUBSCRIPTION_ID = 1;
 
     @NonNull private final Context mContext;
     @NonNull private final TelephonyManager mTelephonyManager;
     @NonNull private final TelephonyManagerShimImpl mTelephonyManagerShim;
     @NonNull private final PackageManager mPackageManager;
-    @NonNull private CarrierPrivilegeAuthenticatorChild mCarrierPrivilegeAuthenticator;
+    @NonNull private TestCarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;
     private final int mCarrierConfigPkgUid = 12345;
     private final String mTestPkg = "com.android.server.connectivity.test";
 
-    public class CarrierPrivilegeAuthenticatorChild extends CarrierPrivilegeAuthenticator {
-        CarrierPrivilegeAuthenticatorChild(@NonNull final Context c,
+    public class TestCarrierPrivilegeAuthenticator extends CarrierPrivilegeAuthenticator {
+        TestCarrierPrivilegeAuthenticator(@NonNull final Context c,
                 @NonNull final TelephonyManager t) {
             super(c, t, mTelephonyManagerShim);
         }
         @Override
         protected int getSlotIndex(int subId) {
+            if (SubscriptionManager.DEFAULT_SUBSCRIPTION_ID == subId) return TEST_SUBSCRIPTION_ID;
             return subId;
         }
     }
@@ -100,7 +101,7 @@
 
     @Before
     public void setUp() throws Exception {
-        doReturn(2).when(mTelephonyManager).getActiveModemCount();
+        doReturn(SUBSCRIPTION_COUNT).when(mTelephonyManager).getActiveModemCount();
         doReturn(mTestPkg).when(mTelephonyManagerShim)
                 .getCarrierServicePackageNameForLogicalSlot(anyInt());
         doReturn(mPackageManager).when(mContext).getPackageManager();
@@ -109,7 +110,7 @@
         doReturn(applicationInfo).when(mPackageManager)
                 .getApplicationInfo(eq(mTestPkg), anyInt());
         mCarrierPrivilegeAuthenticator =
-                new CarrierPrivilegeAuthenticatorChild(mContext, mTelephonyManager);
+                new TestCarrierPrivilegeAuthenticator(mContext, mTelephonyManager);
     }
 
     private IntentFilter getIntentFilter() {
@@ -126,7 +127,6 @@
             verify(mTelephonyManagerShim, atLeastOnce())
                     .addCarrierPrivilegesListener(anyInt(), any(), captor.capture());
         } catch (UnsupportedApiLevelException e) {
-
         }
         return captor.getAllValues();
     }
@@ -160,10 +160,10 @@
         networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
         networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier);
 
-        assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
-                mCarrierConfigPkgUid, networkRequestBuilder.build()));
-        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
-                mCarrierConfigPkgUid + 1, networkRequestBuilder.build()));
+        assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                mCarrierConfigPkgUid + 1, networkRequestBuilder.build().networkCapabilities));
     }
 
     @Test
@@ -192,10 +192,10 @@
         final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
         networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
         networkRequestBuilder.setNetworkSpecifier(telephonyNetworkSpecifier);
-        assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
-                mCarrierConfigPkgUid, networkRequestBuilder.build()));
-        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
-                mCarrierConfigPkgUid + 1, networkRequestBuilder.build()));
+        assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                mCarrierConfigPkgUid + 1, networkRequestBuilder.build().networkCapabilities));
     }
 
     @Test
@@ -215,9 +215,30 @@
                 .getApplicationInfo(eq(mTestPkg), anyInt());
         listener.onCarrierPrivilegesChanged(Collections.emptyList(), new int[] {});
 
-        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
-                mCarrierConfigPkgUid, networkRequestBuilder.build()));
-        assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkRequest(
-                mCarrierConfigPkgUid + 1, networkRequestBuilder.build()));
+        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+        assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                mCarrierConfigPkgUid + 1, networkRequestBuilder.build().networkCapabilities));
+    }
+
+    @Test
+    public void testDefaultSubscription() throws Exception {
+        final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
+        networkRequestBuilder.addTransportType(TRANSPORT_CELLULAR);
+        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+
+        networkRequestBuilder.setNetworkSpecifier(new TelephonyNetworkSpecifier(0));
+        assertTrue(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
+
+        // The builder for NetworkRequest doesn't allow removing the transport as long as a
+        // specifier is set, so unset it first. TODO : fix the builder
+        networkRequestBuilder.setNetworkSpecifier((NetworkSpecifier) null);
+        networkRequestBuilder.removeTransportType(TRANSPORT_CELLULAR);
+        networkRequestBuilder.addTransportType(TRANSPORT_WIFI);
+        networkRequestBuilder.setNetworkSpecifier(new TelephonyNetworkSpecifier(0));
+        assertFalse(mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
+                mCarrierConfigPkgUid, networkRequestBuilder.build().networkCapabilities));
     }
 }