Merge changes from topic "tethering_metrics"

* changes:
  Add tethering stats into statsd
  Injecting tethering stats into statsd
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index bd8fe7c..76c5d5c 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -70,9 +70,9 @@
     canned_fs_config: "canned_fs_config",
     bpfs: [
         "block.o",
-        "clatd.o_mainline",
+        "clatd.o",
         "dscp_policy.o",
-        "netd.o_mainline",
+        "netd.o",
         "offload.o",
         "test.o",
     ],
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index ecb6478..c403548 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -77,6 +77,7 @@
 import com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim;
 import com.android.networkstack.tethering.util.TetheringUtils.ForwardedStats;
 
+import java.io.IOException;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
@@ -1024,7 +1025,7 @@
             map.forEach((k, v) -> {
                 pw.println(String.format("%s: %s", k, v));
             });
-        } catch (ErrnoException e) {
+        } catch (ErrnoException | IOException e) {
             pw.println("Error dumping BPF stats map: " + e);
         }
     }
@@ -1072,7 +1073,7 @@
                 return;
             }
             map.forEach((k, v) -> pw.println(ipv6UpstreamRuletoString(k, v)));
-        } catch (ErrnoException e) {
+        } catch (ErrnoException | IOException e) {
             pw.println("Error dumping IPv6 upstream map: " + e);
         }
     }
@@ -1116,7 +1117,7 @@
         if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_STATS)) {
             try (BpfMap<TetherStatsKey, TetherStatsValue> statsMap = mDeps.getBpfStatsMap()) {
                 dumpRawMap(statsMap, pw);
-            } catch (ErrnoException e) {
+            } catch (ErrnoException | IOException e) {
                 pw.println("Error dumping stats map: " + e);
             }
             return;
@@ -1124,7 +1125,7 @@
         if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_UPSTREAM4)) {
             try (BpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map()) {
                 dumpRawMap(upstreamMap, pw);
-            } catch (ErrnoException e) {
+            } catch (ErrnoException | IOException e) {
                 pw.println("Error dumping IPv4 map: " + e);
             }
             return;
@@ -1195,7 +1196,7 @@
             pw.increaseIndent();
             dumpIpv4ForwardingRuleMap(now, DOWNSTREAM, downstreamMap, pw);
             pw.decreaseIndent();
-        } catch (ErrnoException e) {
+        } catch (ErrnoException | IOException e) {
             pw.println("Error dumping IPv4 map: " + e);
         }
     }
@@ -1220,7 +1221,7 @@
                 }
                 if (v.val > 0) pw.println(String.format("%s: %d", counterName, v.val));
             });
-        } catch (ErrnoException e) {
+        } catch (ErrnoException | IOException e) {
             pw.println("Error dumping counter map: " + e);
         }
     }
@@ -1244,7 +1245,7 @@
                 pw.println(String.format("%d (%s) -> %d (%s)", k.ifIndex, getIfName(k.ifIndex),
                         v.ifIndex, getIfName(v.ifIndex)));
             });
-        } catch (ErrnoException e) {
+        } catch (ErrnoException | IOException e) {
             pw.println("Error dumping dev map: " + e);
         }
         pw.decreaseIndent();
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index e73b7d5..3699f7a 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -59,6 +59,7 @@
 import android.os.HandlerThread;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.VintfRuntimeInfo;
 import android.text.TextUtils;
 import android.util.Base64;
 import android.util.Log;
@@ -84,6 +85,7 @@
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DeviceInfoUtils;
 import com.android.testutils.DumpTestUtils;
 import com.android.testutils.HandlerUtils;
 import com.android.testutils.TapPacketReader;
@@ -1058,19 +1060,33 @@
     }
 
     @Test
-    @IgnoreAfter(Build.VERSION_CODES.Q)
-    public void testTetherUdpV4WithoutBpf() throws Exception {
+    @IgnoreAfter(Build.VERSION_CODES.R)
+    public void testTetherUdpV4UpToR() throws Exception {
         initializeTethering();
         runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader),
                 false /* usingBpf */);
     }
 
+    private static boolean isUdpOffloadSupportedByKernel() {
+        final String kVersionString = VintfRuntimeInfo.getKernelVersion();
+        // Kernel version which is older than 4.14 doesn't support UDP offload absolutely. Kernel
+        // version which is between 4.14 and 5.8 support UDP offload probably. Simply apply kernel
+        // 4.14 to be threshold first and monitor on what devices tests fail for improving the
+        // offload support checking.
+        return DeviceInfoUtils.compareMajorMinorVersion(kVersionString, "4.14") >= 0;
+    }
+
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.R)
-    public void testTetherUdpV4WithBpf() throws Exception {
+    public void testTetherUdpV4AfterR() throws Exception {
         initializeTethering();
+        boolean usingBpf = isUdpOffloadSupportedByKernel();
+        if (!usingBpf) {
+            Log.i(TAG, "testTetherUdpV4AfterR will skip BPF offload test for kernel "
+                    + VintfRuntimeInfo.getKernelVersion());
+        }
         runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader),
-                true /* usingBpf */);
+                usingBpf);
     }
 
     @Nullable
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
index ad2faa0..75c2ad1 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
@@ -358,8 +358,8 @@
         try {
             mTestMap.clear();
             fail("clearing already-closed map should throw");
-        } catch (ErrnoException expected) {
-            assertEquals(OsConstants.EBADF, expected.errno);
+        } catch (IllegalStateException expected) {
+            // ParcelFileDescriptor.getFd throws IllegalStateException: Already closed.
         }
     }
 
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 1fe0e9a..6c78244 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -61,6 +61,7 @@
 bpf {
     name: "block.o",
     srcs: ["block.c"],
+    btf: true,
     cflags: [
         "-Wall",
         "-Werror",
@@ -71,6 +72,7 @@
 bpf {
     name: "dscp_policy.o",
     srcs: ["dscp_policy.c"],
+    btf: true,
     cflags: [
         "-Wall",
         "-Werror",
@@ -97,8 +99,9 @@
 }
 
 bpf {
-    name: "clatd.o_mainline",
+    name: "clatd.o",
     srcs: ["clatd.c"],
+    btf: true,
     cflags: [
         "-Wall",
         "-Werror",
@@ -110,8 +113,9 @@
 }
 
 bpf {
-    name: "netd.o_mainline",
+    name: "netd.o",
     srcs: ["netd.c"],
+    btf: true,
     cflags: [
         "-Wall",
         "-Werror",
diff --git a/bpf_progs/bpf_tethering.h b/bpf_progs/bpf_tethering.h
index b0ec8f6..f9ef6ef 100644
--- a/bpf_progs/bpf_tethering.h
+++ b/bpf_progs/bpf_tethering.h
@@ -73,10 +73,6 @@
 #define STRUCT_SIZE(name, size) _Static_assert(sizeof(name) == (size), "Incorrect struct size.")
 
 
-#define BPF_PATH_TETHER BPF_PATH "tethering/"
-
-#define TETHER_STATS_MAP_PATH BPF_PATH_TETHER "map_offload_tether_stats_map"
-
 typedef uint32_t TetherStatsKey;  // upstream ifindex
 
 typedef struct {
@@ -89,19 +85,9 @@
 } TetherStatsValue;
 STRUCT_SIZE(TetherStatsValue, 6 * 8);  // 48
 
-#define TETHER_LIMIT_MAP_PATH BPF_PATH_TETHER "map_offload_tether_limit_map"
-
 typedef uint32_t TetherLimitKey;    // upstream ifindex
 typedef uint64_t TetherLimitValue;  // in bytes
 
-#define TETHER_DOWNSTREAM6_TC_PROG_RAWIP_NAME "prog_offload_schedcls_tether_downstream6_rawip"
-#define TETHER_DOWNSTREAM6_TC_PROG_ETHER_NAME "prog_offload_schedcls_tether_downstream6_ether"
-
-#define TETHER_DOWNSTREAM6_TC_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM6_TC_PROG_RAWIP_NAME
-#define TETHER_DOWNSTREAM6_TC_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM6_TC_PROG_ETHER_NAME
-
-#define TETHER_DOWNSTREAM6_MAP_PATH BPF_PATH_TETHER "map_offload_tether_downstream6_map"
-
 // For now tethering offload only needs to support downstreams that use 6-byte MAC addresses,
 // because all downstream types that are currently supported (WiFi, USB, Bluetooth and
 // Ethernet) have 6-byte MAC addresses.
@@ -121,8 +107,6 @@
 } Tether6Value;
 STRUCT_SIZE(Tether6Value, 4 + 14 + 2);  // 20
 
-#define TETHER_DOWNSTREAM64_MAP_PATH BPF_PATH_TETHER "map_offload_tether_downstream64_map"
-
 typedef struct {
     uint32_t iif;              // The input interface index
     uint8_t dstMac[ETH_ALEN];  // destination ethernet mac address (zeroed iff rawip ingress)
@@ -146,14 +130,6 @@
 } TetherDownstream64Value;
 STRUCT_SIZE(TetherDownstream64Value, 4 + 14 + 2 + 4 + 4 + 2 + 2 + 8);  // 40
 
-#define TETHER_UPSTREAM6_TC_PROG_RAWIP_NAME "prog_offload_schedcls_tether_upstream6_rawip"
-#define TETHER_UPSTREAM6_TC_PROG_ETHER_NAME "prog_offload_schedcls_tether_upstream6_ether"
-
-#define TETHER_UPSTREAM6_TC_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_UPSTREAM6_TC_PROG_RAWIP_NAME
-#define TETHER_UPSTREAM6_TC_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_UPSTREAM6_TC_PROG_ETHER_NAME
-
-#define TETHER_UPSTREAM6_MAP_PATH BPF_PATH_TETHER "map_offload_tether_upstream6_map"
-
 typedef struct {
     uint32_t iif;              // The input interface index
     uint8_t dstMac[ETH_ALEN];  // destination ethernet mac address (zeroed iff rawip ingress)
@@ -162,23 +138,6 @@
 } TetherUpstream6Key;
 STRUCT_SIZE(TetherUpstream6Key, 12);
 
-#define TETHER_DOWNSTREAM4_TC_PROG_RAWIP_NAME "prog_offload_schedcls_tether_downstream4_rawip"
-#define TETHER_DOWNSTREAM4_TC_PROG_ETHER_NAME "prog_offload_schedcls_tether_downstream4_ether"
-
-#define TETHER_DOWNSTREAM4_TC_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM4_TC_PROG_RAWIP_NAME
-#define TETHER_DOWNSTREAM4_TC_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM4_TC_PROG_ETHER_NAME
-
-#define TETHER_DOWNSTREAM4_MAP_PATH BPF_PATH_TETHER "map_offload_tether_downstream4_map"
-
-
-#define TETHER_UPSTREAM4_TC_PROG_RAWIP_NAME "prog_offload_schedcls_tether_upstream4_rawip"
-#define TETHER_UPSTREAM4_TC_PROG_ETHER_NAME "prog_offload_schedcls_tether_upstream4_ether"
-
-#define TETHER_UPSTREAM4_TC_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_UPSTREAM4_TC_PROG_RAWIP_NAME
-#define TETHER_UPSTREAM4_TC_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_UPSTREAM4_TC_PROG_ETHER_NAME
-
-#define TETHER_UPSTREAM4_MAP_PATH BPF_PATH_TETHER "map_offload_tether_upstream4_map"
-
 typedef struct {
     uint32_t iif;              // The input interface index
     uint8_t dstMac[ETH_ALEN];  // destination ethernet mac address (zeroed iff rawip ingress)
@@ -202,16 +161,4 @@
 } Tether4Value;
 STRUCT_SIZE(Tether4Value, 4 + 14 + 2 + 16 + 16 + 2 + 2 + 8);  // 64
 
-#define TETHER_DOWNSTREAM_XDP_PROG_RAWIP_NAME "prog_offload_xdp_tether_downstream_rawip"
-#define TETHER_DOWNSTREAM_XDP_PROG_ETHER_NAME "prog_offload_xdp_tether_downstream_ether"
-
-#define TETHER_DOWNSTREAM_XDP_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM_XDP_PROG_RAWIP_NAME
-#define TETHER_DOWNSTREAM_XDP_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM_XDP_PROG_ETHER_NAME
-
-#define TETHER_UPSTREAM_XDP_PROG_RAWIP_NAME "prog_offload_xdp_tether_upstream_rawip"
-#define TETHER_UPSTREAM_XDP_PROG_ETHER_NAME "prog_offload_xdp_tether_upstream_ether"
-
-#define TETHER_UPSTREAM_XDP_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_UPSTREAM_XDP_PROG_RAWIP_NAME
-#define TETHER_UPSTREAM_XDP_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_UPSTREAM_XDP_PROG_ETHER_NAME
-
 #undef STRUCT_SIZE
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index fe9a871..f3f675f 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -194,7 +194,7 @@
     BpfConfig enabledRules = getConfig(UID_RULES_CONFIGURATION_KEY);
 
     UidOwnerValue* uidEntry = bpf_uid_owner_map_lookup_elem(&uid);
-    uint8_t uidRules = uidEntry ? uidEntry->rule : 0;
+    uint32_t uidRules = uidEntry ? uidEntry->rule : 0;
     uint32_t allowed_iif = uidEntry ? uidEntry->iif : 0;
 
     if (enabledRules) {
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index 92a774c..896bc09 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -355,88 +355,10 @@
 
 DEFINE_BPF_MAP_GRW(tether_upstream4_map, HASH, Tether4Key, Tether4Value, 1024, AID_NETWORK_STACK)
 
-static inline __always_inline int do_forward4(struct __sk_buff* skb, const bool is_ethernet,
-        const bool downstream, const bool updatetime) {
-    // Require ethernet dst mac address to be our unicast address.
-    if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE;
-
-    // Must be meta-ethernet IPv4 frame
-    if (skb->protocol != htons(ETH_P_IP)) return TC_ACT_PIPE;
-
-    const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
-
-    // Since the program never writes via DPA (direct packet access) auto-pull/unclone logic does
-    // not trigger and thus we need to manually make sure we can read packet headers via DPA.
-    // Note: this is a blind best effort pull, which may fail or pull less - this doesn't matter.
-    // It has to be done early cause it will invalidate any skb->data/data_end derived pointers.
-    try_make_writable(skb, l2_header_size + IP4_HLEN + TCP_HLEN);
-
-    void* data = (void*)(long)skb->data;
-    const void* data_end = (void*)(long)skb->data_end;
-    struct ethhdr* eth = is_ethernet ? data : NULL;  // used iff is_ethernet
-    struct iphdr* ip = is_ethernet ? (void*)(eth + 1) : data;
-
-    // Must have (ethernet and) ipv4 header
-    if (data + l2_header_size + sizeof(*ip) > data_end) return TC_ACT_PIPE;
-
-    // Ethertype - if present - must be IPv4
-    if (is_ethernet && (eth->h_proto != htons(ETH_P_IP))) return TC_ACT_PIPE;
-
-    // IP version must be 4
-    if (ip->version != 4) TC_PUNT(INVALID_IP_VERSION);
-
-    // We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header
-    if (ip->ihl != 5) TC_PUNT(HAS_IP_OPTIONS);
-
-    // Calculate the IPv4 one's complement checksum of the IPv4 header.
-    __wsum sum4 = 0;
-    for (int i = 0; i < sizeof(*ip) / sizeof(__u16); ++i) {
-        sum4 += ((__u16*)ip)[i];
-    }
-    // Note that sum4 is guaranteed to be non-zero by virtue of ip4->version == 4
-    sum4 = (sum4 & 0xFFFF) + (sum4 >> 16);  // collapse u32 into range 1 .. 0x1FFFE
-    sum4 = (sum4 & 0xFFFF) + (sum4 >> 16);  // collapse any potential carry into u16
-    // for a correct checksum we should get *a* zero, but sum4 must be positive, ie 0xFFFF
-    if (sum4 != 0xFFFF) TC_PUNT(CHECKSUM);
-
-    // Minimum IPv4 total length is the size of the header
-    if (ntohs(ip->tot_len) < sizeof(*ip)) TC_PUNT(TRUNCATED_IPV4);
-
-    // We are incapable of dealing with IPv4 fragments
-    if (ip->frag_off & ~htons(IP_DF)) TC_PUNT(IS_IP_FRAG);
-
-    // Cannot decrement during forward if already zero or would be zero,
-    // Let the kernel's stack handle these cases and generate appropriate ICMP errors.
-    if (ip->ttl <= 1) TC_PUNT(LOW_TTL);
-
-    // If we cannot update the 'last_used' field due to lack of bpf_ktime_get_boot_ns() helper,
-    // then it is not safe to offload UDP due to the small conntrack timeouts, as such,
-    // in such a situation we can only support TCP.  This also has the added nice benefit of
-    // using a separate error counter, and thus making it obvious which version of the program
-    // is loaded.
-    if (!updatetime && ip->protocol != IPPROTO_TCP) TC_PUNT(NON_TCP);
-
-    // We do not support offloading anything besides IPv4 TCP and UDP, due to need for NAT,
-    // but no need to check this if !updatetime due to check immediately above.
-    if (updatetime && (ip->protocol != IPPROTO_TCP) && (ip->protocol != IPPROTO_UDP))
-        TC_PUNT(NON_TCP_UDP);
-
-    // We want to make sure that the compiler will, in the !updatetime case, entirely optimize
-    // out all the non-tcp logic.  Also note that at this point is_udp === !is_tcp.
-    const bool is_tcp = !updatetime || (ip->protocol == IPPROTO_TCP);
-
-    // This is a bit of a hack to make things easier on the bpf verifier.
-    // (In particular I believe the Linux 4.14 kernel's verifier can get confused later on about
-    // what offsets into the packet are valid and can spuriously reject the program, this is
-    // because it fails to realize that is_tcp && !is_tcp is impossible)
-    //
-    // For both TCP & UDP we'll need to read and modify the src/dst ports, which so happen to
-    // always be in the first 4 bytes of the L4 header.  Additionally for UDP we'll need access
-    // to the checksum field which is in bytes 7 and 8.  While for TCP we'll need to read the
-    // TCP flags (at offset 13) and access to the checksum field (2 bytes at offset 16).
-    // As such we *always* need access to at least 8 bytes.
-    if (data + l2_header_size + sizeof(*ip) + 8 > data_end) TC_PUNT(SHORT_L4_HEADER);
-
+static inline __always_inline int do_forward4_bottom(struct __sk_buff* skb,
+        const int l2_header_size, void* data, const void* data_end,
+        struct ethhdr* eth, struct iphdr* ip, const bool is_ethernet,
+        const bool downstream, const bool updatetime, const bool is_tcp) {
     struct tcphdr* tcph = is_tcp ? (void*)(ip + 1) : NULL;
     struct udphdr* udph = is_tcp ? NULL : (void*)(ip + 1);
 
@@ -625,6 +547,102 @@
     return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */);
 }
 
+static inline __always_inline int do_forward4(struct __sk_buff* skb, const bool is_ethernet,
+        const bool downstream, const bool updatetime) {
+    // Require ethernet dst mac address to be our unicast address.
+    if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE;
+
+    // Must be meta-ethernet IPv4 frame
+    if (skb->protocol != htons(ETH_P_IP)) return TC_ACT_PIPE;
+
+    const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
+
+    // Since the program never writes via DPA (direct packet access) auto-pull/unclone logic does
+    // not trigger and thus we need to manually make sure we can read packet headers via DPA.
+    // Note: this is a blind best effort pull, which may fail or pull less - this doesn't matter.
+    // It has to be done early cause it will invalidate any skb->data/data_end derived pointers.
+    try_make_writable(skb, l2_header_size + IP4_HLEN + TCP_HLEN);
+
+    void* data = (void*)(long)skb->data;
+    const void* data_end = (void*)(long)skb->data_end;
+    struct ethhdr* eth = is_ethernet ? data : NULL;  // used iff is_ethernet
+    struct iphdr* ip = is_ethernet ? (void*)(eth + 1) : data;
+
+    // Must have (ethernet and) ipv4 header
+    if (data + l2_header_size + sizeof(*ip) > data_end) return TC_ACT_PIPE;
+
+    // Ethertype - if present - must be IPv4
+    if (is_ethernet && (eth->h_proto != htons(ETH_P_IP))) return TC_ACT_PIPE;
+
+    // IP version must be 4
+    if (ip->version != 4) TC_PUNT(INVALID_IP_VERSION);
+
+    // We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header
+    if (ip->ihl != 5) TC_PUNT(HAS_IP_OPTIONS);
+
+    // Calculate the IPv4 one's complement checksum of the IPv4 header.
+    __wsum sum4 = 0;
+    for (int i = 0; i < sizeof(*ip) / sizeof(__u16); ++i) {
+        sum4 += ((__u16*)ip)[i];
+    }
+    // Note that sum4 is guaranteed to be non-zero by virtue of ip4->version == 4
+    sum4 = (sum4 & 0xFFFF) + (sum4 >> 16);  // collapse u32 into range 1 .. 0x1FFFE
+    sum4 = (sum4 & 0xFFFF) + (sum4 >> 16);  // collapse any potential carry into u16
+    // for a correct checksum we should get *a* zero, but sum4 must be positive, ie 0xFFFF
+    if (sum4 != 0xFFFF) TC_PUNT(CHECKSUM);
+
+    // Minimum IPv4 total length is the size of the header
+    if (ntohs(ip->tot_len) < sizeof(*ip)) TC_PUNT(TRUNCATED_IPV4);
+
+    // We are incapable of dealing with IPv4 fragments
+    if (ip->frag_off & ~htons(IP_DF)) TC_PUNT(IS_IP_FRAG);
+
+    // Cannot decrement during forward if already zero or would be zero,
+    // Let the kernel's stack handle these cases and generate appropriate ICMP errors.
+    if (ip->ttl <= 1) TC_PUNT(LOW_TTL);
+
+    // If we cannot update the 'last_used' field due to lack of bpf_ktime_get_boot_ns() helper,
+    // then it is not safe to offload UDP due to the small conntrack timeouts, as such,
+    // in such a situation we can only support TCP.  This also has the added nice benefit of
+    // using a separate error counter, and thus making it obvious which version of the program
+    // is loaded.
+    if (!updatetime && ip->protocol != IPPROTO_TCP) TC_PUNT(NON_TCP);
+
+    // We do not support offloading anything besides IPv4 TCP and UDP, due to need for NAT,
+    // but no need to check this if !updatetime due to check immediately above.
+    if (updatetime && (ip->protocol != IPPROTO_TCP) && (ip->protocol != IPPROTO_UDP))
+        TC_PUNT(NON_TCP_UDP);
+
+    // We want to make sure that the compiler will, in the !updatetime case, entirely optimize
+    // out all the non-tcp logic.  Also note that at this point is_udp === !is_tcp.
+    const bool is_tcp = !updatetime || (ip->protocol == IPPROTO_TCP);
+
+    // This is a bit of a hack to make things easier on the bpf verifier.
+    // (In particular I believe the Linux 4.14 kernel's verifier can get confused later on about
+    // what offsets into the packet are valid and can spuriously reject the program, this is
+    // because it fails to realize that is_tcp && !is_tcp is impossible)
+    //
+    // For both TCP & UDP we'll need to read and modify the src/dst ports, which so happen to
+    // always be in the first 4 bytes of the L4 header.  Additionally for UDP we'll need access
+    // to the checksum field which is in bytes 7 and 8.  While for TCP we'll need to read the
+    // TCP flags (at offset 13) and access to the checksum field (2 bytes at offset 16).
+    // As such we *always* need access to at least 8 bytes.
+    if (data + l2_header_size + sizeof(*ip) + 8 > data_end) TC_PUNT(SHORT_L4_HEADER);
+
+    // We're forcing the compiler to emit two copies of the following code, optimized
+    // separately for is_tcp being true or false.  This simplifies the resulting bpf
+    // byte code sufficiently that the 4.14 bpf verifier is able to keep track of things.
+    // Without this (updatetime == true) case would fail to bpf verify on 4.14 even
+    // if the underlying requisite kernel support (bpf_ktime_get_boot_ns) was backported.
+    if (is_tcp) {
+      return do_forward4_bottom(skb, l2_header_size, data, data_end, eth, ip,
+                                is_ethernet, downstream, updatetime, /* is_tcp */ true);
+    } else {
+      return do_forward4_bottom(skb, l2_header_size, data, data_end, eth, ip,
+                                is_ethernet, downstream, updatetime, /* is_tcp */ false);
+    }
+}
+
 // Full featured (required) implementations for 5.8+ kernels (these are S+ by definition)
 
 DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_rawip$5_8", AID_ROOT, AID_NETWORK_STACK,
diff --git a/framework-t/src/android/net/NetworkStatsCollection.java b/framework-t/src/android/net/NetworkStatsCollection.java
index b59a890..29ea772 100644
--- a/framework-t/src/android/net/NetworkStatsCollection.java
+++ b/framework-t/src/android/net/NetworkStatsCollection.java
@@ -694,6 +694,26 @@
         }
     }
 
+    /**
+     * Remove histories which contains or is before the cutoff timestamp.
+     * @hide
+     */
+    public void removeHistoryBefore(long cutoffMillis) {
+        final ArrayList<Key> knownKeys = new ArrayList<>();
+        knownKeys.addAll(mStats.keySet());
+
+        for (Key key : knownKeys) {
+            final NetworkStatsHistory history = mStats.get(key);
+            if (history.getStart() > cutoffMillis) continue;
+
+            history.removeBucketsStartingBefore(cutoffMillis);
+            if (history.size() == 0) {
+                mStats.remove(key);
+            }
+            mDirty = true;
+        }
+    }
+
     private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) {
         if (startMillis < mStartMillis) mStartMillis = startMillis;
         if (endMillis > mEndMillis) mEndMillis = endMillis;
diff --git a/framework-t/src/android/net/NetworkStatsHistory.java b/framework-t/src/android/net/NetworkStatsHistory.java
index 301fef9..b45d44d 100644
--- a/framework-t/src/android/net/NetworkStatsHistory.java
+++ b/framework-t/src/android/net/NetworkStatsHistory.java
@@ -680,19 +680,21 @@
     }
 
     /**
-     * Remove buckets older than requested cutoff.
+     * Remove buckets that start older than requested cutoff.
+     *
+     * This method will remove any bucket that contains any data older than the requested
+     * cutoff, even if that same bucket includes some data from after the cutoff.
+     *
      * @hide
      */
-    public void removeBucketsBefore(long cutoff) {
+    public void removeBucketsStartingBefore(final long cutoff) {
         // TODO: Consider use getIndexBefore.
         int i;
         for (i = 0; i < bucketCount; i++) {
             final long curStart = bucketStart[i];
-            final long curEnd = curStart + bucketDuration;
 
-            // cutoff happens before or during this bucket; everything before
-            // this bucket should be removed.
-            if (curEnd > cutoff) break;
+            // This bucket starts after or at the cutoff, so it should be kept.
+            if (curStart >= cutoff) break;
         }
 
         if (i > 0) {
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index a174fe3..d16a6f5 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -2589,9 +2589,24 @@
      * {@hide}
      */
     public ConnectivityManager(Context context, IConnectivityManager service) {
+        this(context, service, true /* newStatic */);
+    }
+
+    private ConnectivityManager(Context context, IConnectivityManager service, boolean newStatic) {
         mContext = Objects.requireNonNull(context, "missing context");
         mService = Objects.requireNonNull(service, "missing IConnectivityManager");
-        sInstance = this;
+        // sInstance is accessed without a lock, so it may actually be reassigned several times with
+        // different ConnectivityManager, but that's still OK considering its usage.
+        if (sInstance == null && newStatic) {
+            final Context appContext = mContext.getApplicationContext();
+            // Don't create static ConnectivityManager instance again to prevent infinite loop.
+            // If the application context is null, we're either in the system process or
+            // it's the application context very early in app initialization. In both these
+            // cases, the passed-in Context will not be freed, so it's safe to pass it to the
+            // service. http://b/27532714 .
+            sInstance = new ConnectivityManager(appContext != null ? appContext : context, service,
+                    false /* newStatic */);
+        }
     }
 
     /** {@hide} */
diff --git a/framework/src/android/net/LinkProperties.java b/framework/src/android/net/LinkProperties.java
index 126a5ff..a8f707e 100644
--- a/framework/src/android/net/LinkProperties.java
+++ b/framework/src/android/net/LinkProperties.java
@@ -1366,6 +1366,21 @@
     }
 
     /**
+     * Returns true if this link has a throw route.
+     *
+     * @return {@code true} if there is an exclude route, {@code false} otherwise.
+     * @hide
+     */
+    public boolean hasExcludeRoute() {
+        for (RouteInfo r : mRoutes) {
+            if (r.getType() == RouteInfo.RTN_THROW) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Compares this {@code LinkProperties} interface name against the target
      *
      * @param target LinkProperties to compare.
diff --git a/framework/src/android/net/ProfileNetworkPreference.java b/framework/src/android/net/ProfileNetworkPreference.java
index fb271e3..fdcab02 100644
--- a/framework/src/android/net/ProfileNetworkPreference.java
+++ b/framework/src/android/net/ProfileNetworkPreference.java
@@ -120,8 +120,8 @@
     public String toString() {
         return "ProfileNetworkPreference{"
                 + "mPreference=" + getPreference()
-                + "mIncludedUids=" + mIncludedUids.toString()
-                + "mExcludedUids=" + mExcludedUids.toString()
+                + "mIncludedUids=" + Arrays.toString(mIncludedUids)
+                + "mExcludedUids=" + Arrays.toString(mExcludedUids)
                 + "mPreferenceEnterpriseId=" + mPreferenceEnterpriseId
                 + '}';
     }
diff --git a/framework/src/android/net/QosCallbackException.java b/framework/src/android/net/QosCallbackException.java
index ed6eb15..b80cff4 100644
--- a/framework/src/android/net/QosCallbackException.java
+++ b/framework/src/android/net/QosCallbackException.java
@@ -46,8 +46,10 @@
             EX_TYPE_FILTER_NONE,
             EX_TYPE_FILTER_NETWORK_RELEASED,
             EX_TYPE_FILTER_SOCKET_NOT_BOUND,
+            EX_TYPE_FILTER_SOCKET_NOT_CONNECTED,
             EX_TYPE_FILTER_NOT_SUPPORTED,
             EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED,
+            EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ExceptionType {}
@@ -65,10 +67,16 @@
     public static final int EX_TYPE_FILTER_SOCKET_NOT_BOUND = 2;
 
     /** {@hide} */
-    public static final int EX_TYPE_FILTER_NOT_SUPPORTED = 3;
+    public static final int EX_TYPE_FILTER_SOCKET_NOT_CONNECTED = 3;
 
     /** {@hide} */
-    public static final int EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED = 4;
+    public static final int EX_TYPE_FILTER_NOT_SUPPORTED = 4;
+
+    /** {@hide} */
+    public static final int EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED = 5;
+
+    /** {@hide} */
+    public static final int EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED = 6;
 
     /**
      * Creates exception based off of a type and message.  Not all types of exceptions accept a
@@ -83,12 +91,17 @@
                 return new QosCallbackException(new NetworkReleasedException());
             case EX_TYPE_FILTER_SOCKET_NOT_BOUND:
                 return new QosCallbackException(new SocketNotBoundException());
+            case EX_TYPE_FILTER_SOCKET_NOT_CONNECTED:
+                return new QosCallbackException(new SocketNotConnectedException());
             case EX_TYPE_FILTER_NOT_SUPPORTED:
                 return new QosCallbackException(new UnsupportedOperationException(
                         "This device does not support the specified filter"));
             case EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED:
                 return new QosCallbackException(
                         new SocketLocalAddressChangedException());
+            case EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED:
+                return new QosCallbackException(
+                        new SocketRemoteAddressChangedException());
             default:
                 Log.wtf(TAG, "create: No case setup for exception type: '" + type + "'");
                 return new QosCallbackException(
diff --git a/framework/src/android/net/QosFilter.java b/framework/src/android/net/QosFilter.java
index 5c1c3cc..b432644 100644
--- a/framework/src/android/net/QosFilter.java
+++ b/framework/src/android/net/QosFilter.java
@@ -90,5 +90,15 @@
      */
     public abstract boolean matchesRemoteAddress(@NonNull InetAddress address,
             int startPort, int endPort);
+
+    /**
+     * Determines whether or not the parameter will be matched with this filter.
+     *
+     * @param protocol the protocol such as TCP or UDP included in IP packet filter set of a QoS
+     *                 flow assigned on {@link Network}.
+     * @return whether the parameters match the socket type of the filter
+     * @hide
+     */
+    public abstract boolean matchesProtocol(int protocol);
 }
 
diff --git a/framework/src/android/net/QosFilterParcelable.java b/framework/src/android/net/QosFilterParcelable.java
index da3b2cf..6e71fa3 100644
--- a/framework/src/android/net/QosFilterParcelable.java
+++ b/framework/src/android/net/QosFilterParcelable.java
@@ -104,7 +104,7 @@
         if (mQosFilter instanceof QosSocketFilter) {
             dest.writeInt(QOS_SOCKET_FILTER);
             final QosSocketFilter qosSocketFilter = (QosSocketFilter) mQosFilter;
-            qosSocketFilter.getQosSocketInfo().writeToParcel(dest, 0);
+            qosSocketFilter.getQosSocketInfo().writeToParcelWithoutFd(dest, 0);
             return;
         }
         dest.writeInt(NO_FILTER_PRESENT);
diff --git a/framework/src/android/net/QosSocketFilter.java b/framework/src/android/net/QosSocketFilter.java
index 69da7f4..5ceeb67 100644
--- a/framework/src/android/net/QosSocketFilter.java
+++ b/framework/src/android/net/QosSocketFilter.java
@@ -18,6 +18,13 @@
 
 import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE;
 import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_CONNECTED;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_STREAM;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -74,19 +81,34 @@
      * 2. In the scenario that the socket is now bound to a different local address, which can
      *    happen in the case of UDP, then
      *    {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED} is returned.
+     * 3. In the scenario that the UDP socket changed remote address, then
+     *    {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED} is returned.
+     *
      * @return validation error code
      */
     @Override
     public int validate() {
-        final InetSocketAddress sa = getAddressFromFileDescriptor();
-        if (sa == null) {
-            return QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND;
+        final InetSocketAddress sa = getLocalAddressFromFileDescriptor();
+
+        if (sa == null || (sa.getAddress().isAnyLocalAddress() && sa.getPort() == 0)) {
+            return EX_TYPE_FILTER_SOCKET_NOT_BOUND;
         }
 
         if (!sa.equals(mQosSocketInfo.getLocalSocketAddress())) {
             return EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
         }
 
+        if (mQosSocketInfo.getRemoteSocketAddress() != null) {
+            final InetSocketAddress da = getRemoteAddressFromFileDescriptor();
+            if (da == null) {
+                return EX_TYPE_FILTER_SOCKET_NOT_CONNECTED;
+            }
+
+            if (!da.equals(mQosSocketInfo.getRemoteSocketAddress())) {
+                return EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED;
+            }
+        }
+
         return EX_TYPE_FILTER_NONE;
     }
 
@@ -98,17 +120,14 @@
      * @return the local address
      */
     @Nullable
-    private InetSocketAddress getAddressFromFileDescriptor() {
+    private InetSocketAddress getLocalAddressFromFileDescriptor() {
         final ParcelFileDescriptor parcelFileDescriptor = mQosSocketInfo.getParcelFileDescriptor();
-        if (parcelFileDescriptor == null) return null;
-
         final FileDescriptor fd = parcelFileDescriptor.getFileDescriptor();
-        if (fd == null) return null;
 
         final SocketAddress address;
         try {
             address = Os.getsockname(fd);
-        } catch (final ErrnoException e) {
+        } catch (ErrnoException e) {
             Log.e(TAG, "getAddressFromFileDescriptor: getLocalAddress exception", e);
             return null;
         }
@@ -119,6 +138,31 @@
     }
 
     /**
+     * The remote address of the socket's connected.
+     *
+     * <p>Note: If the socket is no longer connected, null is returned.
+     *
+     * @return the remote address
+     */
+    @Nullable
+    private InetSocketAddress getRemoteAddressFromFileDescriptor() {
+        final ParcelFileDescriptor parcelFileDescriptor = mQosSocketInfo.getParcelFileDescriptor();
+        final FileDescriptor fd = parcelFileDescriptor.getFileDescriptor();
+
+        final SocketAddress address;
+        try {
+            address = Os.getpeername(fd);
+        } catch (ErrnoException e) {
+            Log.e(TAG, "getAddressFromFileDescriptor: getRemoteAddress exception", e);
+            return null;
+        }
+        if (address instanceof InetSocketAddress) {
+            return (InetSocketAddress) address;
+        }
+        return null;
+    }
+
+    /**
      * The network used with this filter.
      *
      * @return the registered {@link Network}
@@ -156,6 +200,18 @@
     }
 
     /**
+     * @inheritDoc
+     */
+    @Override
+    public boolean matchesProtocol(final int protocol) {
+        if ((mQosSocketInfo.getSocketType() == SOCK_STREAM && protocol == IPPROTO_TCP)
+                || (mQosSocketInfo.getSocketType() == SOCK_DGRAM && protocol == IPPROTO_UDP)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Called from {@link QosSocketFilter#matchesLocalAddress(InetAddress, int, int)}
      * and {@link QosSocketFilter#matchesRemoteAddress(InetAddress, int, int)} with the
      * filterSocketAddress coming from {@link QosSocketInfo#getLocalSocketAddress()}.
@@ -174,6 +230,7 @@
             final int startPort, final int endPort) {
         return startPort <= filterSocketAddress.getPort()
                 && endPort >= filterSocketAddress.getPort()
-                && filterSocketAddress.getAddress().equals(address);
+                && (address.isAnyLocalAddress()
+                        || filterSocketAddress.getAddress().equals(address));
     }
 }
diff --git a/framework/src/android/net/QosSocketInfo.java b/framework/src/android/net/QosSocketInfo.java
index 39c2f33..49ac22b 100644
--- a/framework/src/android/net/QosSocketInfo.java
+++ b/framework/src/android/net/QosSocketInfo.java
@@ -165,25 +165,28 @@
     /* Parcelable methods */
     private QosSocketInfo(final Parcel in) {
         mNetwork = Objects.requireNonNull(Network.CREATOR.createFromParcel(in));
-        mParcelFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in);
+        final boolean withFd = in.readBoolean();
+        if (withFd) {
+            mParcelFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in);
+        } else {
+            mParcelFileDescriptor = null;
+        }
 
-        final int localAddressLength = in.readInt();
-        mLocalSocketAddress = readSocketAddress(in, localAddressLength);
-
-        final int remoteAddressLength = in.readInt();
-        mRemoteSocketAddress = remoteAddressLength == 0 ? null
-                : readSocketAddress(in, remoteAddressLength);
+        mLocalSocketAddress = readSocketAddress(in);
+        mRemoteSocketAddress = readSocketAddress(in);
 
         mSocketType = in.readInt();
     }
 
-    private @NonNull InetSocketAddress readSocketAddress(final Parcel in, final int addressLength) {
-        final byte[] address = new byte[addressLength];
-        in.readByteArray(address);
+    private InetSocketAddress readSocketAddress(final Parcel in) {
+        final byte[] addrBytes = in.createByteArray();
+        if (addrBytes == null) {
+            return null;
+        }
         final int port = in.readInt();
 
         try {
-            return new InetSocketAddress(InetAddress.getByAddress(address), port);
+            return new InetSocketAddress(InetAddress.getByAddress(addrBytes), port);
         } catch (final UnknownHostException e) {
             /* This can never happen. UnknownHostException will never be thrown
                since the address provided is numeric and non-null. */
@@ -198,20 +201,35 @@
 
     @Override
     public void writeToParcel(@NonNull final Parcel dest, final int flags) {
-        mNetwork.writeToParcel(dest, 0);
-        mParcelFileDescriptor.writeToParcel(dest, 0);
+        writeToParcelInternal(dest, flags, /*includeFd=*/ true);
+    }
 
-        final byte[] localAddress = mLocalSocketAddress.getAddress().getAddress();
-        dest.writeInt(localAddress.length);
-        dest.writeByteArray(localAddress);
+    /**
+     * Used when sending QosSocketInfo to telephony, which does not need access to the socket FD.
+     * @hide
+     */
+    public void writeToParcelWithoutFd(@NonNull final Parcel dest, final int flags) {
+        writeToParcelInternal(dest, flags, /*includeFd=*/ false);
+    }
+
+    private void writeToParcelInternal(
+            @NonNull final Parcel dest, final int flags, boolean includeFd) {
+        mNetwork.writeToParcel(dest, 0);
+
+        if (includeFd) {
+            dest.writeBoolean(true);
+            mParcelFileDescriptor.writeToParcel(dest, 0);
+        } else {
+            dest.writeBoolean(false);
+        }
+
+        dest.writeByteArray(mLocalSocketAddress.getAddress().getAddress());
         dest.writeInt(mLocalSocketAddress.getPort());
 
         if (mRemoteSocketAddress == null) {
-            dest.writeInt(0);
+            dest.writeByteArray(null);
         } else {
-            final byte[] remoteAddress = mRemoteSocketAddress.getAddress().getAddress();
-            dest.writeInt(remoteAddress.length);
-            dest.writeByteArray(remoteAddress);
+            dest.writeByteArray(mRemoteSocketAddress.getAddress().getAddress());
             dest.writeInt(mRemoteSocketAddress.getPort());
         }
         dest.writeInt(mSocketType);
diff --git a/framework/src/android/net/SocketNotConnectedException.java b/framework/src/android/net/SocketNotConnectedException.java
new file mode 100644
index 0000000..fa2a615
--- /dev/null
+++ b/framework/src/android/net/SocketNotConnectedException.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ * Thrown when a previously bound socket becomes unbound.
+ *
+ * @hide
+ */
+public class SocketNotConnectedException extends Exception {
+    /** @hide */
+    public SocketNotConnectedException() {
+        super("The socket is not connected");
+    }
+}
diff --git a/framework/src/android/net/SocketRemoteAddressChangedException.java b/framework/src/android/net/SocketRemoteAddressChangedException.java
new file mode 100644
index 0000000..ecaeebc
--- /dev/null
+++ b/framework/src/android/net/SocketRemoteAddressChangedException.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ * Thrown when the local address of the socket has changed.
+ *
+ * @hide
+ */
+public class SocketRemoteAddressChangedException extends Exception {
+    /** @hide */
+    public SocketRemoteAddressChangedException() {
+        super("The remote address of the socket changed");
+    }
+}
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 4086e4e..ea57bac 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -738,7 +738,13 @@
         String type = service.getServiceType();
         int port = service.getPort();
         byte[] textRecord = service.getTxtRecord();
-        return mMDnsManager.registerService(regId, name, type, port, textRecord, IFACE_IDX_ANY);
+        final Network network = service.getNetwork();
+        final int registerInterface = getNetworkInterfaceIndex(network);
+        if (network != null && registerInterface == IFACE_IDX_ANY) {
+            Log.e(TAG, "Interface to register service on not found");
+            return false;
+        }
+        return mMDnsManager.registerService(regId, name, type, port, textRecord, registerInterface);
     }
 
     private boolean unregisterService(int regId) {
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
index fe27335..09782fd 100644
--- a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -194,7 +194,7 @@
         }
 
         final NetworkInterfaceState iface = new NetworkInterfaceState(
-                ifaceName, hwAddress, mHandler, mContext, ipConfig, nc, this, mDeps);
+                ifaceName, hwAddress, mHandler, mContext, ipConfig, nc, getProvider(), mDeps);
         mTrackingInterfaces.put(ifaceName, iface);
         updateCapabilityFilter();
     }
@@ -361,7 +361,7 @@
         private final String mHwAddress;
         private final Handler mHandler;
         private final Context mContext;
-        private final NetworkFactory mNetworkFactory;
+        private final NetworkProvider mNetworkProvider;
         private final Dependencies mDeps;
 
         private static String sTcpBufferSizes = null;  // Lazy initialized.
@@ -471,14 +471,14 @@
 
         NetworkInterfaceState(String ifaceName, String hwAddress, Handler handler, Context context,
                 @NonNull IpConfiguration ipConfig, @NonNull NetworkCapabilities capabilities,
-                NetworkFactory networkFactory, Dependencies deps) {
+                NetworkProvider networkProvider, Dependencies deps) {
             name = ifaceName;
             mIpConfig = Objects.requireNonNull(ipConfig);
             mCapabilities = Objects.requireNonNull(capabilities);
             mLegacyType = getLegacyType(mCapabilities);
             mHandler = handler;
             mContext = context;
-            mNetworkFactory = networkFactory;
+            mNetworkProvider = networkProvider;
             mDeps = deps;
             mHwAddress = hwAddress;
         }
@@ -575,7 +575,7 @@
                     .setLegacyExtraInfo(mHwAddress)
                     .build();
             mNetworkAgent = mDeps.makeEthernetNetworkAgent(mContext, mHandler.getLooper(),
-                    mCapabilities, mLinkProperties, config, mNetworkFactory.getProvider(),
+                    mCapabilities, mLinkProperties, config, mNetworkProvider,
                     new EthernetNetworkAgent.Callbacks() {
                         @Override
                         public void onNetworkUnwanted() {
diff --git a/service-t/src/com/android/server/net/NetworkStatsObservers.java b/service-t/src/com/android/server/net/NetworkStatsObservers.java
index fdfc893..df4e7f5 100644
--- a/service-t/src/com/android/server/net/NetworkStatsObservers.java
+++ b/service-t/src/com/android/server/net/NetworkStatsObservers.java
@@ -18,6 +18,7 @@
 
 import static android.app.usage.NetworkStatsManager.MIN_THRESHOLD_BYTES;
 
+import android.annotation.NonNull;
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -38,10 +39,12 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.PerUidCounter;
 
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -52,16 +55,26 @@
  */
 class NetworkStatsObservers {
     private static final String TAG = "NetworkStatsObservers";
+    private static final boolean LOG = true;
     private static final boolean LOGV = false;
 
     private static final int MSG_REGISTER = 1;
     private static final int MSG_UNREGISTER = 2;
     private static final int MSG_UPDATE_STATS = 3;
 
+    private static final int DUMP_USAGE_REQUESTS_COUNT = 200;
+
+    // The maximum number of request allowed per uid before an exception is thrown.
+    @VisibleForTesting
+    static final int MAX_REQUESTS_PER_UID = 100;
+
     // All access to this map must be done from the handler thread.
     // indexed by DataUsageRequest#requestId
     private final SparseArray<RequestInfo> mDataUsageRequests = new SparseArray<>();
 
+    // Request counters per uid, this is thread safe.
+    private final PerUidCounter mDataUsageRequestsPerUid = new PerUidCounter(MAX_REQUESTS_PER_UID);
+
     // Sequence number of DataUsageRequests
     private final AtomicInteger mNextDataUsageRequestId = new AtomicInteger();
 
@@ -77,13 +90,16 @@
      *
      * @return the normalized request wrapped within {@link RequestInfo}.
      */
-    public DataUsageRequest register(Context context, DataUsageRequest inputRequest,
-            IUsageCallback callback, int callingUid, @NetworkStatsAccess.Level int accessLevel) {
+    public DataUsageRequest register(@NonNull Context context,
+            @NonNull DataUsageRequest inputRequest, @NonNull IUsageCallback callback,
+            int callingPid, int callingUid, @NonNull String callingPackage,
+            @NetworkStatsAccess.Level int accessLevel) {
         DataUsageRequest request = buildRequest(context, inputRequest, callingUid);
-        RequestInfo requestInfo = buildRequestInfo(request, callback, callingUid,
-                accessLevel);
+        RequestInfo requestInfo = buildRequestInfo(request, callback, callingPid, callingUid,
+                callingPackage, accessLevel);
+        if (LOG) Log.d(TAG, "Registering observer for " + requestInfo);
+        mDataUsageRequestsPerUid.incrementCountOrThrow(callingUid);
 
-        if (LOGV) Log.v(TAG, "Registering observer for " + request);
         getHandler().sendMessage(mHandler.obtainMessage(MSG_REGISTER, requestInfo));
         return request;
     }
@@ -172,7 +188,7 @@
         RequestInfo requestInfo;
         requestInfo = mDataUsageRequests.get(request.requestId);
         if (requestInfo == null) {
-            if (LOGV) Log.v(TAG, "Trying to unregister unknown request " + request);
+            if (LOG) Log.d(TAG, "Trying to unregister unknown request " + request);
             return;
         }
         if (Process.SYSTEM_UID != callingUid && requestInfo.mCallingUid != callingUid) {
@@ -180,8 +196,9 @@
             return;
         }
 
-        if (LOGV) Log.v(TAG, "Unregistering " + request);
+        if (LOG) Log.d(TAG, "Unregistering " + requestInfo);
         mDataUsageRequests.remove(request.requestId);
+        mDataUsageRequestsPerUid.decrementCountOrThrow(callingUid);
         requestInfo.unlinkDeathRecipient();
         requestInfo.callCallback(NetworkStatsManager.CALLBACK_RELEASED);
     }
@@ -214,18 +231,19 @@
     }
 
     private RequestInfo buildRequestInfo(DataUsageRequest request, IUsageCallback callback,
-            int callingUid, @NetworkStatsAccess.Level int accessLevel) {
+            int callingPid, int callingUid, @NonNull String callingPackage,
+            @NetworkStatsAccess.Level int accessLevel) {
         if (accessLevel <= NetworkStatsAccess.Level.USER) {
-            return new UserUsageRequestInfo(this, request, callback, callingUid,
-                    accessLevel);
+            return new UserUsageRequestInfo(this, request, callback, callingPid,
+                    callingUid, callingPackage, accessLevel);
         } else {
             // Safety check in case a new access level is added and we forgot to update this
             if (accessLevel < NetworkStatsAccess.Level.DEVICESUMMARY) {
                 throw new IllegalArgumentException(
                         "accessLevel " + accessLevel + " is less than DEVICESUMMARY.");
             }
-            return new NetworkUsageRequestInfo(this, request, callback, callingUid,
-                    accessLevel);
+            return new NetworkUsageRequestInfo(this, request, callback, callingPid,
+                    callingUid, callingPackage, accessLevel);
         }
     }
 
@@ -237,18 +255,22 @@
         private final NetworkStatsObservers mStatsObserver;
         protected final DataUsageRequest mRequest;
         private final IUsageCallback mCallback;
+        protected final int mCallingPid;
         protected final int mCallingUid;
+        protected final String mCallingPackage;
         protected final @NetworkStatsAccess.Level int mAccessLevel;
         protected NetworkStatsRecorder mRecorder;
         protected NetworkStatsCollection mCollection;
 
         RequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
-                IUsageCallback callback, int callingUid,
-                    @NetworkStatsAccess.Level int accessLevel) {
+                IUsageCallback callback, int callingPid, int callingUid,
+                @NonNull String callingPackage, @NetworkStatsAccess.Level int accessLevel) {
             mStatsObserver = statsObserver;
             mRequest = request;
             mCallback = callback;
+            mCallingPid = callingPid;
             mCallingUid = callingUid;
+            mCallingPackage = callingPackage;
             mAccessLevel = accessLevel;
 
             try {
@@ -269,7 +291,8 @@
 
         @Override
         public String toString() {
-            return "RequestInfo from uid:" + mCallingUid
+            return "RequestInfo from pid/uid:" + mCallingPid + "/" + mCallingUid
+                    + "(" + mCallingPackage + ")"
                     + " for " + mRequest + " accessLevel:" + mAccessLevel;
         }
 
@@ -338,9 +361,10 @@
 
     private static class NetworkUsageRequestInfo extends RequestInfo {
         NetworkUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
-                IUsageCallback callback, int callingUid,
-                    @NetworkStatsAccess.Level int accessLevel) {
-            super(statsObserver, request, callback, callingUid, accessLevel);
+                IUsageCallback callback, int callingPid, int callingUid,
+                @NonNull String callingPackage, @NetworkStatsAccess.Level int accessLevel) {
+            super(statsObserver, request, callback, callingPid, callingUid, callingPackage,
+                    accessLevel);
         }
 
         @Override
@@ -380,9 +404,10 @@
 
     private static class UserUsageRequestInfo extends RequestInfo {
         UserUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
-                    IUsageCallback callback, int callingUid,
-                    @NetworkStatsAccess.Level int accessLevel) {
-            super(statsObserver, request, callback, callingUid, accessLevel);
+                IUsageCallback callback, int callingPid, int callingUid,
+                @NonNull String callingPackage, @NetworkStatsAccess.Level int accessLevel) {
+            super(statsObserver, request, callback, callingPid, callingUid,
+                    callingPackage, accessLevel);
         }
 
         @Override
@@ -448,4 +473,10 @@
             mCurrentTime = currentTime;
         }
     }
+
+    public void dump(IndentingPrintWriter pw) {
+        for (int i = 0; i < Math.min(mDataUsageRequests.size(), DUMP_USAGE_REQUESTS_COUNT); i++) {
+            pw.println(mDataUsageRequests.valueAt(i));
+        }
+    }
 }
diff --git a/service-t/src/com/android/server/net/NetworkStatsRecorder.java b/service-t/src/com/android/server/net/NetworkStatsRecorder.java
index f62765d..6f070d7 100644
--- a/service-t/src/com/android/server/net/NetworkStatsRecorder.java
+++ b/service-t/src/com/android/server/net/NetworkStatsRecorder.java
@@ -455,6 +455,73 @@
         }
     }
 
+    /**
+     * Rewriter that will remove any histories or persisted data points before the
+     * specified cutoff time, only writing data back when modified.
+     */
+    public static class RemoveDataBeforeRewriter implements FileRotator.Rewriter {
+        private final NetworkStatsCollection mTemp;
+        private final long mCutoffMills;
+
+        public RemoveDataBeforeRewriter(long bucketDuration, long cutoffMills) {
+            mTemp = new NetworkStatsCollection(bucketDuration);
+            mCutoffMills = cutoffMills;
+        }
+
+        @Override
+        public void reset() {
+            mTemp.reset();
+        }
+
+        @Override
+        public void read(InputStream in) throws IOException {
+            mTemp.read(in);
+            mTemp.clearDirty();
+            mTemp.removeHistoryBefore(mCutoffMills);
+        }
+
+        @Override
+        public boolean shouldWrite() {
+            return mTemp.isDirty();
+        }
+
+        @Override
+        public void write(OutputStream out) throws IOException {
+            mTemp.write(out);
+        }
+    }
+
+    /**
+     * Remove persisted data which contains or is before the cutoff timestamp.
+     */
+    public void removeDataBefore(long cutoffMillis) throws IOException {
+        if (mRotator != null) {
+            try {
+                mRotator.rewriteAll(new RemoveDataBeforeRewriter(
+                        mBucketDuration, cutoffMillis));
+            } catch (IOException e) {
+                Log.wtf(TAG, "problem importing netstats", e);
+                recoverFromWtf();
+            } catch (OutOfMemoryError e) {
+                Log.wtf(TAG, "problem importing netstats", e);
+                recoverFromWtf();
+            }
+        }
+
+        // Clean up any pending stats
+        if (mPending != null) {
+            mPending.removeHistoryBefore(cutoffMillis);
+        }
+        if (mSinceBoot != null) {
+            mSinceBoot.removeHistoryBefore(cutoffMillis);
+        }
+
+        final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
+        if (complete != null) {
+            complete.removeHistoryBefore(cutoffMillis);
+        }
+    }
+
     public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
         if (mPending != null) {
             pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 82b1fb5..b2d8b5e 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -159,8 +159,11 @@
 import java.time.ZoneOffset;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
@@ -374,9 +377,19 @@
 
     private long mLastStatsSessionPoll;
 
-    /** Map from UID to number of opened sessions */
-    @GuardedBy("mOpenSessionCallsPerUid")
+    private final Object mOpenSessionCallsLock = new Object();
+    /**
+     * Map from UID to number of opened sessions. This is used for rate-limt an app to open
+     * session frequently
+     */
+    @GuardedBy("mOpenSessionCallsLock")
     private final SparseIntArray mOpenSessionCallsPerUid = new SparseIntArray();
+    /**
+     * Map from key {@code OpenSessionKey} to count of opened sessions. This is for recording
+     * the caller of open session and it is only for debugging.
+     */
+    @GuardedBy("mOpenSessionCallsLock")
+    private final HashMap<OpenSessionKey, Integer> mOpenSessionCallsPerCaller = new HashMap<>();
 
     private final static int DUMP_STATS_SESSION_COUNT = 20;
 
@@ -407,6 +420,44 @@
                 Clock.systemUTC());
     }
 
+    /**
+     * This class is a key that used in {@code mOpenSessionCallsPerCaller} to identify the count of
+     * the caller.
+     */
+    private static class OpenSessionKey {
+        public final int uid;
+        public final String packageName;
+
+        OpenSessionKey(int uid, @NonNull String packageName) {
+            this.uid = uid;
+            this.packageName = packageName;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("{");
+            sb.append("uid=").append(uid).append(",");
+            sb.append("package=").append(packageName);
+            sb.append("}");
+            return sb.toString();
+        }
+
+        @Override
+        public boolean equals(@NonNull Object o) {
+            if (this == o) return true;
+            if (o.getClass() != getClass()) return false;
+
+            final OpenSessionKey key = (OpenSessionKey) o;
+            return this.uid == key.uid && TextUtils.equals(this.packageName, key.packageName);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(uid, packageName);
+        }
+    }
+
     private final class NetworkStatsHandler extends Handler {
         NetworkStatsHandler(@NonNull Looper looper) {
             super(looper);
@@ -794,16 +845,27 @@
         return openSessionInternal(flags, callingPackage);
     }
 
-    private boolean isRateLimitedForPoll(int callingUid) {
-        if (callingUid == android.os.Process.SYSTEM_UID) {
-            return false;
-        }
-
+    private boolean isRateLimitedForPoll(@NonNull OpenSessionKey key) {
         final long lastCallTime;
         final long now = SystemClock.elapsedRealtime();
-        synchronized (mOpenSessionCallsPerUid) {
-            int calls = mOpenSessionCallsPerUid.get(callingUid, 0);
-            mOpenSessionCallsPerUid.put(callingUid, calls + 1);
+
+        synchronized (mOpenSessionCallsLock) {
+            Integer callsPerCaller = mOpenSessionCallsPerCaller.get(key);
+            if (callsPerCaller == null) {
+                mOpenSessionCallsPerCaller.put((key), 1);
+            } else {
+                mOpenSessionCallsPerCaller.put(key, Integer.sum(callsPerCaller, 1));
+            }
+
+            int callsPerUid = mOpenSessionCallsPerUid.get(key.uid, 0);
+            mOpenSessionCallsPerUid.put(key.uid, callsPerUid + 1);
+
+            if (key.uid == android.os.Process.SYSTEM_UID) {
+                return false;
+            }
+
+            // To avoid a non-system user to be rate-limited after system users open sessions,
+            // so update mLastStatsSessionPoll after checked if the uid is SYSTEM_UID.
             lastCallTime = mLastStatsSessionPoll;
             mLastStatsSessionPoll = now;
         }
@@ -811,7 +873,7 @@
         return now - lastCallTime < POLL_RATE_LIMIT_MS;
     }
 
-    private int restrictFlagsForCaller(int flags) {
+    private int restrictFlagsForCaller(int flags, @NonNull String callingPackage) {
         // All non-privileged callers are not allowed to turn off POLL_ON_OPEN.
         final boolean isPrivileged = PermissionUtils.checkAnyPermissionOf(mContext,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
@@ -821,14 +883,15 @@
         }
         // Non-system uids are rate limited for POLL_ON_OPEN.
         final int callingUid = Binder.getCallingUid();
-        flags = isRateLimitedForPoll(callingUid)
+        final OpenSessionKey key = new OpenSessionKey(callingUid, callingPackage);
+        flags = isRateLimitedForPoll(key)
                 ? flags & (~NetworkStatsManager.FLAG_POLL_ON_OPEN)
                 : flags;
         return flags;
     }
 
     private INetworkStatsSession openSessionInternal(final int flags, final String callingPackage) {
-        final int restrictedFlags = restrictFlagsForCaller(flags);
+        final int restrictedFlags = restrictFlagsForCaller(flags, callingPackage);
         if ((restrictedFlags & (NetworkStatsManager.FLAG_POLL_ON_OPEN
                 | NetworkStatsManager.FLAG_POLL_FORCE)) != 0) {
             final long ident = Binder.clearCallingIdentity();
@@ -1279,13 +1342,14 @@
         Objects.requireNonNull(request.template, "NetworkTemplate is null");
         Objects.requireNonNull(callback, "callback is null");
 
-        int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        final int callingUid = Binder.getCallingUid();
         @NetworkStatsAccess.Level int accessLevel = checkAccessLevel(callingPackage);
         DataUsageRequest normalizedRequest;
         final long token = Binder.clearCallingIdentity();
         try {
             normalizedRequest = mStatsObservers.register(mContext,
-                    request, callback, callingUid, accessLevel);
+                    request, callback, callingPid, callingUid, callingPackage, accessLevel);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -1937,6 +2001,9 @@
         for (int uid : uids) {
             deleteKernelTagData(uid);
         }
+
+       // TODO: Remove the UID's entries from mOpenSessionCallsPerUid and
+       // mOpenSessionCallsPerCaller
     }
 
     /**
@@ -2060,25 +2127,21 @@
             pw.decreaseIndent();
 
             // Get the top openSession callers
-            final SparseIntArray calls;
-            synchronized (mOpenSessionCallsPerUid) {
-                calls = mOpenSessionCallsPerUid.clone();
+            final HashMap calls;
+            synchronized (mOpenSessionCallsLock) {
+                calls = new HashMap<>(mOpenSessionCallsPerCaller);
             }
-
-            final int N = calls.size();
-            final long[] values = new long[N];
-            for (int j = 0; j < N; j++) {
-                values[j] = ((long) calls.valueAt(j) << 32) | calls.keyAt(j);
-            }
-            Arrays.sort(values);
-
-            pw.println("Top openSession callers (uid=count):");
+            final List<Map.Entry<OpenSessionKey, Integer>> list = new ArrayList<>(calls.entrySet());
+            Collections.sort(list,
+                    (left, right) -> Integer.compare(left.getValue(), right.getValue()));
+            final int num = list.size();
+            final int end = Math.max(0, num - DUMP_STATS_SESSION_COUNT);
+            pw.println("Top openSession callers:");
             pw.increaseIndent();
-            final int end = Math.max(0, N - DUMP_STATS_SESSION_COUNT);
-            for (int j = N - 1; j >= end; j--) {
-                final int uid = (int) (values[j] & 0xffffffff);
-                final int count = (int) (values[j] >> 32);
-                pw.print(uid); pw.print("="); pw.println(count);
+            for (int j = num - 1; j >= end; j--) {
+                final Map.Entry<OpenSessionKey, Integer> entry = list.get(j);
+                pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue());
+
             }
             pw.decreaseIndent();
             pw.println();
@@ -2098,6 +2161,13 @@
                 }
             });
             pw.decreaseIndent();
+            pw.println();
+
+            pw.println("Stats Observers:");
+            pw.increaseIndent();
+            mStatsObservers.dump(pw);
+            pw.decreaseIndent();
+            pw.println();
 
             pw.println("Dev stats:");
             pw.increaseIndent();
diff --git a/service/Android.bp b/service/Android.bp
index 25b970a..7dbdc92 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -216,6 +216,11 @@
     apex_available: [
         "com.android.tethering",
     ],
+    optimize: {
+        enabled: true,
+        shrink: true,
+        proguard_flags_files: ["proguard.flags"],
+    },
     lint: { strict_updatability_linting: true },
 }
 
diff --git a/service/ServiceConnectivityResources/Android.bp b/service/ServiceConnectivityResources/Android.bp
index f491cc7..02b2875 100644
--- a/service/ServiceConnectivityResources/Android.bp
+++ b/service/ServiceConnectivityResources/Android.bp
@@ -23,6 +23,7 @@
     name: "ServiceConnectivityResources",
     sdk_version: "module_30",
     min_sdk_version: "30",
+    target_sdk_version: "33",
     resource_dirs: [
         "res",
     ],
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index 4b21569..c7223fc 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -111,5 +111,13 @@
 # From mdns-aidl-interface
 rule android.net.mdns.aidl.** android.net.connectivity.@0
 
+# From nearby-service, including proto
+rule service.proto.** com.android.server.nearby.@0
+rule androidx.annotation.Keep* com.android.server.nearby.@0
+rule androidx.collection.** com.android.server.nearby.@0
+rule androidx.core.** com.android.server.nearby.@0
+rule androidx.versionedparcelable.** com.android.server.nearby.@0
+rule com.google.common.** com.android.server.nearby.@0
+
 # Remaining are connectivity sources in com.android.server and com.android.server.connectivity:
 # TODO: move to a subpackage of com.android.connectivity (such as com.android.connectivity.server)
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 3e98edb..473c9e3 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -88,7 +88,7 @@
         }                                   \
     } while (0)
 
-const std::string uidMatchTypeToString(uint8_t match) {
+const std::string uidMatchTypeToString(uint32_t match) {
     std::string matchType;
     FLAG_MSG_TRANS(matchType, HAPPY_BOX_MATCH, match);
     FLAG_MSG_TRANS(matchType, PENALTY_BOX_MATCH, match);
@@ -272,7 +272,7 @@
     if (oldMatch.ok()) {
         UidOwnerValue newMatch = {
                 .iif = (match == IIF_MATCH) ? 0 : oldMatch.value().iif,
-                .rule = static_cast<uint8_t>(oldMatch.value().rule & ~match),
+                .rule = oldMatch.value().rule & ~match,
         };
         if (newMatch.rule == 0) {
             RETURN_IF_NOT_OK(mUidOwnerMap.deleteValue(uid));
@@ -296,13 +296,13 @@
     if (oldMatch.ok()) {
         UidOwnerValue newMatch = {
                 .iif = iif ? iif : oldMatch.value().iif,
-                .rule = static_cast<uint8_t>(oldMatch.value().rule | match),
+                .rule = oldMatch.value().rule | match,
         };
         RETURN_IF_NOT_OK(mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY));
     } else {
         UidOwnerValue newMatch = {
                 .iif = iif,
-                .rule = static_cast<uint8_t>(match),
+                .rule = match,
         };
         RETURN_IF_NOT_OK(mUidOwnerMap.writeValue(uid, newMatch, BPF_ANY));
     }
diff --git a/service/proguard.flags b/service/proguard.flags
new file mode 100644
index 0000000..94397ab
--- /dev/null
+++ b/service/proguard.flags
@@ -0,0 +1,18 @@
+# Make sure proguard keeps all connectivity classes
+# TODO: instead of keeping everything, consider listing only "entry points"
+# (service loader, JNI registered methods, etc) and letting the optimizer do its job
+-keep class android.net.** { *; }
+-keep class com.android.connectivity.** { *; }
+-keep class com.android.net.** { *; }
+-keep class !com.android.server.nearby.**,com.android.server.** { *; }
+
+# Prevent proguard from stripping out any nearby-service and fast-pair-lite-protos fields.
+-keep class com.android.server.nearby.NearbyService { *; }
+-keep class com.android.server.nearby.service.proto { *; }
+
+# The lite proto runtime uses reflection to access fields based on the names in
+# the schema, keep all the fields.
+# This replicates the base proguard rule used by the build by default
+# (proguard_basic_keeps.flags), but needs to be specified here because the
+# com.google.protobuf package is jarjared to the below package.
+-keepclassmembers class * extends com.android.connectivity.com.google.protobuf.MessageLite { <fields>; }
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 4183ac6..66ef9e9 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -610,13 +610,6 @@
     // Handle private DNS validation status updates.
     private static final int EVENT_PRIVATE_DNS_VALIDATION_UPDATE = 38;
 
-    /**
-     * used to remove a network request, either a listener or a real request and call unavailable
-     * arg1 = UID of caller
-     * obj  = NetworkRequest
-     */
-    private static final int EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE = 39;
-
      /**
       * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has
       * been tested.
@@ -1194,6 +1187,7 @@
     /**
      * Keeps track of the number of requests made under different uids.
      */
+    // TODO: Remove the hack and use com.android.net.module.util.PerUidCounter instead.
     public static class PerUidCounter {
         private final int mMaxCountPerUid;
 
@@ -2628,7 +2622,7 @@
         verifyCallingUidAndPackage(callingPackageName, mDeps.getCallingUid());
         enforceChangePermission(callingPackageName, callingAttributionTag);
         if (mProtectedNetworks.contains(networkType)) {
-            enforceConnectivityRestrictedNetworksPermission();
+            enforceConnectivityRestrictedNetworksPermission(true /* checkUidsAllowedList */);
         }
 
         InetAddress addr;
@@ -2982,18 +2976,35 @@
                 android.Manifest.permission.NETWORK_SETTINGS);
     }
 
-    private void enforceConnectivityRestrictedNetworksPermission() {
-        try {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS,
-                    "ConnectivityService");
-            return;
-        } catch (SecurityException e) { /* fallback to ConnectivityInternalPermission */ }
-        //  TODO: Remove this fallback check after all apps have declared
-        //   CONNECTIVITY_USE_RESTRICTED_NETWORKS.
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CONNECTIVITY_INTERNAL,
-                "ConnectivityService");
+    private boolean checkConnectivityRestrictedNetworksPermission(int callingUid,
+            boolean checkUidsAllowedList) {
+        if (PermissionUtils.checkAnyPermissionOf(mContext,
+                android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS)) {
+            return true;
+        }
+
+        // fallback to ConnectivityInternalPermission
+        // TODO: Remove this fallback check after all apps have declared
+        //  CONNECTIVITY_USE_RESTRICTED_NETWORKS.
+        if (PermissionUtils.checkAnyPermissionOf(mContext,
+                android.Manifest.permission.CONNECTIVITY_INTERNAL)) {
+            return true;
+        }
+
+        // Check whether uid is in allowed on restricted networks list.
+        if (checkUidsAllowedList
+                && mPermissionMonitor.isUidAllowedOnRestrictedNetworks(callingUid)) {
+            return true;
+        }
+        return false;
+    }
+
+    private void enforceConnectivityRestrictedNetworksPermission(boolean checkUidsAllowedList) {
+        final int callingUid = mDeps.getCallingUid();
+        if (!checkConnectivityRestrictedNetworksPermission(callingUid, checkUidsAllowedList)) {
+            throw new SecurityException("ConnectivityService: user " + callingUid
+                    + " has no permission to access restricted network.");
+        }
     }
 
     private void enforceKeepalivePermission() {
@@ -4495,7 +4506,7 @@
 
     private boolean hasCarrierPrivilegeForNetworkCaps(final int callingUid,
             @NonNull final NetworkCapabilities caps) {
-        if (SdkLevel.isAtLeastT() && mCarrierPrivilegeAuthenticator != null) {
+        if (mCarrierPrivilegeAuthenticator != null) {
             return mCarrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities(
                     callingUid, caps);
         }
@@ -4525,7 +4536,6 @@
 
     private void handleRegisterNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
         ensureRunningOnConnectivityServiceThread();
-        NetworkRequest requestToBeReleased = null;
         for (final NetworkRequestInfo nri : nris) {
             mNetworkRequestInfoLogs.log("REGISTER " + nri);
             checkNrisConsistency(nri);
@@ -4540,13 +4550,6 @@
                         }
                     }
                 }
-                if (req.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
-                    if (!hasCarrierPrivilegeForNetworkCaps(nri.mUid, req.networkCapabilities)
-                            && !checkConnectivityRestrictedNetworksPermission(
-                                    nri.mPid, nri.mUid)) {
-                        requestToBeReleased = req;
-                    }
-                }
             }
 
             // If this NRI has a satisfier already, it is replacing an older request that
@@ -4558,11 +4561,6 @@
             }
         }
 
-        if (requestToBeReleased != null) {
-            releaseNetworkRequestAndCallOnUnavailable(requestToBeReleased);
-            return;
-        }
-
         if (mFlags.noRematchAllRequestsOnRegister()) {
             rematchNetworksAndRequests(nris);
         } else {
@@ -5402,11 +5400,6 @@
                             /* callOnUnavailable */ false);
                     break;
                 }
-                case EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE: {
-                    handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1,
-                            /* callOnUnavailable */ true);
-                    break;
-                }
                 case EVENT_SET_ACCEPT_UNVALIDATED: {
                     Network network = (Network) msg.obj;
                     handleSetAcceptUnvalidated(network, toBool(msg.arg1), toBool(msg.arg2));
@@ -6625,7 +6618,7 @@
             case REQUEST:
                 networkCapabilities = new NetworkCapabilities(networkCapabilities);
                 enforceNetworkRequestPermissions(networkCapabilities, callingPackageName,
-                        callingAttributionTag);
+                        callingAttributionTag, callingUid);
                 // TODO: this is incorrect. We mark the request as metered or not depending on
                 //  the state of the app when the request is filed, but we never change the
                 //  request if the app changes network state. http://b/29964605
@@ -6715,26 +6708,19 @@
     }
 
     private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
-            String callingPackageName, String callingAttributionTag) {
+            String callingPackageName, String callingAttributionTag, final int callingUid) {
         if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
-            if (!networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
-                enforceConnectivityRestrictedNetworksPermission();
+            // For T+ devices, callers with carrier privilege could request with CBS capabilities.
+            if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
+                    && hasCarrierPrivilegeForNetworkCaps(callingUid, networkCapabilities)) {
+                return;
             }
+            enforceConnectivityRestrictedNetworksPermission(true /* checkUidsAllowedList */);
         } else {
             enforceChangePermission(callingPackageName, callingAttributionTag);
         }
     }
 
-    private boolean checkConnectivityRestrictedNetworksPermission(int callerPid, int callerUid) {
-        if (checkAnyPermissionOf(callerPid, callerUid,
-                android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS)
-                || checkAnyPermissionOf(callerPid, callerUid,
-                android.Manifest.permission.CONNECTIVITY_INTERNAL)) {
-            return true;
-        }
-        return false;
-    }
-
     @Override
     public boolean requestBandwidthUpdate(Network network) {
         enforceAccessPermission();
@@ -6793,7 +6779,7 @@
         final int callingUid = mDeps.getCallingUid();
         networkCapabilities = new NetworkCapabilities(networkCapabilities);
         enforceNetworkRequestPermissions(networkCapabilities, callingPackageName,
-                callingAttributionTag);
+                callingAttributionTag, callingUid);
         enforceMeteredApnPolicy(networkCapabilities);
         ensureRequestableCapabilities(networkCapabilities);
         ensureSufficientPermissionsForRequest(networkCapabilities,
@@ -6916,13 +6902,6 @@
                 EVENT_RELEASE_NETWORK_REQUEST, mDeps.getCallingUid(), 0, networkRequest));
     }
 
-    private void releaseNetworkRequestAndCallOnUnavailable(NetworkRequest networkRequest) {
-        ensureNetworkRequestHasType(networkRequest);
-        mHandler.sendMessage(mHandler.obtainMessage(
-                EVENT_RELEASE_NETWORK_REQUEST_AND_CALL_UNAVAILABLE, mDeps.getCallingUid(), 0,
-                networkRequest));
-    }
-
     private void handleRegisterNetworkProvider(NetworkProviderInfo npi) {
         if (mNetworkProviderInfos.containsKey(npi.messenger)) {
             // Avoid creating duplicates. even if an app makes a direct AIDL call.
@@ -8087,7 +8066,8 @@
                 && nc.getOwnerUid() != Process.SYSTEM_UID
                 && lp.getInterfaceName() != null
                 && (lp.hasIpv4DefaultRoute() || lp.hasIpv4UnreachableDefaultRoute())
-                && (lp.hasIpv6DefaultRoute() || lp.hasIpv6UnreachableDefaultRoute());
+                && (lp.hasIpv6DefaultRoute() || lp.hasIpv6UnreachableDefaultRoute())
+                && !lp.hasExcludeRoute();
     }
 
     private static UidRangeParcel[] toUidRangeStableParcels(final @NonNull Set<UidRange> ranges) {
@@ -10624,7 +10604,11 @@
         if (callback == null) throw new IllegalArgumentException("callback must be non-null");
 
         if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
-            enforceConnectivityRestrictedNetworksPermission();
+            // TODO: Check allowed list here and ensure that either a) any QoS callback registered
+            //  on this network is unregistered when the app loses permission or b) no QoS
+            //  callbacks are sent for restricted networks unless the app currently has permission
+            //  to access restricted networks.
+            enforceConnectivityRestrictedNetworksPermission(false /* checkUidsAllowedList */);
         }
         mQosCallbackTracker.registerCallback(callback, filter, nai);
     }
@@ -10676,7 +10660,10 @@
         Objects.requireNonNull(profile);
 
         if (preferences.size() == 0) {
-            preferences.add((new ProfileNetworkPreference.Builder()).build());
+            final ProfileNetworkPreference pref = new ProfileNetworkPreference.Builder()
+                    .setPreference(ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT)
+                    .build();
+            preferences.add(pref);
         }
 
         PermissionUtils.enforceNetworkStackPermission(mContext);
@@ -10694,12 +10681,14 @@
 
         final List<ProfileNetworkPreferenceList.Preference> preferenceList =
                 new ArrayList<ProfileNetworkPreferenceList.Preference>();
-        boolean allowFallback = true;
+        boolean hasDefaultPreference = false;
         for (final ProfileNetworkPreference preference : preferences) {
             final NetworkCapabilities nc;
+            boolean allowFallback = true;
             switch (preference.getPreference()) {
                 case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT:
                     nc = null;
+                    hasDefaultPreference = true;
                     if (preference.getPreferenceEnterpriseId() != 0) {
                         throw new IllegalArgumentException(
                                 "Invalid enterprise identifier in setProfileNetworkPreferences");
@@ -10709,6 +10698,14 @@
                     allowFallback = false;
                     // continue to process the enterprise preference.
                 case ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE:
+                    // This code is needed even though there is a check later on,
+                    // because isRangeAlreadyInPreferenceList assumes that every preference
+                    // has a UID list.
+                    if (hasDefaultPreference) {
+                        throw new IllegalArgumentException(
+                                "Default profile preference should not be set along with other "
+                                        + "preference");
+                    }
                     if (!isEnterpriseIdentifierValid(preference.getPreferenceEnterpriseId())) {
                         throw new IllegalArgumentException(
                                 "Invalid enterprise identifier in setProfileNetworkPreferences");
@@ -10732,6 +10729,10 @@
             }
             preferenceList.add(new ProfileNetworkPreferenceList.Preference(
                     profile, nc, allowFallback));
+            if (hasDefaultPreference && preferenceList.size() > 1) {
+                throw new IllegalArgumentException(
+                        "Default profile preference should not be set along with other preference");
+            }
         }
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_PROFILE_NETWORK_PREFERENCE,
                 new Pair<>(preferenceList, listener)));
@@ -10776,12 +10777,6 @@
         return false;
     }
 
-    private void validateNetworkCapabilitiesOfProfileNetworkPreference(
-            @Nullable final NetworkCapabilities nc) {
-        if (null == nc) return; // Null caps are always allowed. It means to remove the setting.
-        ensureRequestableCapabilities(nc);
-    }
-
     private ArraySet<NetworkRequestInfo> createNrisFromProfileNetworkPreferences(
             @NonNull final ProfileNetworkPreferenceList prefs) {
         final ArraySet<NetworkRequestInfo> result = new ArraySet<>();
@@ -10832,10 +10827,19 @@
     private void handleSetProfileNetworkPreference(
             @NonNull final List<ProfileNetworkPreferenceList.Preference> preferenceList,
             @Nullable final IOnCompleteListener listener) {
+        /*
+         * handleSetProfileNetworkPreference is always called for single user.
+         * preferenceList only contains preferences for different uids within the same user
+         * (enforced by getUidListToBeAppliedForNetworkPreference).
+         * Clear all the existing preferences for the user before applying new preferences.
+         *
+         */
+        mProfileNetworkPreferences = mProfileNetworkPreferences.withoutUser(
+                preferenceList.get(0).user);
         for (final ProfileNetworkPreferenceList.Preference preference : preferenceList) {
-            validateNetworkCapabilitiesOfProfileNetworkPreference(preference.capabilities);
             mProfileNetworkPreferences = mProfileNetworkPreferences.plus(preference);
         }
+
         removeDefaultNetworkRequestsForPreference(PREFERENCE_ORDER_PROFILE);
         addPerAppDefaultNetworkRequests(
                 createNrisFromProfileNetworkPreferences(mProfileNetworkPreferences));
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index c02d9cf..8d99cb4 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -421,7 +421,14 @@
         if (appInfo == null) return false;
         // Check whether package's uid is in allowed on restricted networks uid list. If so, this
         // uid can have netd system permission.
-        return mUidsAllowedOnRestrictedNetworks.contains(appInfo.uid);
+        return isUidAllowedOnRestrictedNetworks(appInfo.uid);
+    }
+
+    /**
+     * Returns whether the given uid is in allowed on restricted networks list.
+     */
+    public synchronized boolean isUidAllowedOnRestrictedNetworks(final int uid) {
+        return mUidsAllowedOnRestrictedNetworks.contains(uid);
     }
 
     @VisibleForTesting
diff --git a/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java b/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java
index 71f342d..5bafef9 100644
--- a/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java
+++ b/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java
@@ -70,23 +70,33 @@
     /**
      * Returns a new object consisting of this object plus the passed preference.
      *
-     * If a preference already exists for the same user, it will be replaced by the passed
-     * preference. Passing a Preference object containing a null capabilities object is equivalent
-     * to (and indeed, implemented as) removing the preference for this user.
+     * It is not expected that unwanted preference already exists for the same user.
+     * All preferences for the user that were previously configured should be cleared before
+     * adding a new preference.
+     * Passing a Preference object containing a null capabilities object is equivalent
+     * to removing the preference for this user.
      */
     public ProfileNetworkPreferenceList plus(@NonNull final Preference pref) {
-        final ArrayList<Preference> newPrefs = new ArrayList<>();
-        for (final Preference existingPref : preferences) {
-            if (!existingPref.user.equals(pref.user)) {
-                newPrefs.add(existingPref);
-            }
-        }
+        final ArrayList<Preference> newPrefs = new ArrayList<>(preferences);
         if (null != pref.capabilities) {
             newPrefs.add(pref);
         }
         return new ProfileNetworkPreferenceList(newPrefs);
     }
 
+    /**
+     * Remove all preferences corresponding to a user.
+     */
+    public ProfileNetworkPreferenceList withoutUser(UserHandle user) {
+        final ArrayList<Preference> newPrefs = new ArrayList<>();
+        for (final Preference existingPref : preferences) {
+            if (!existingPref.user.equals(user)) {
+                newPrefs.add(existingPref);
+            }
+        }
+        return new ProfileNetworkPreferenceList(newPrefs);
+    }
+
     public boolean isEmpty() {
         return preferences.isEmpty();
     }
diff --git a/service/src/com/android/server/connectivity/QosCallbackAgentConnection.java b/service/src/com/android/server/connectivity/QosCallbackAgentConnection.java
index 534dbe7..e682026 100644
--- a/service/src/com/android/server/connectivity/QosCallbackAgentConnection.java
+++ b/service/src/com/android/server/connectivity/QosCallbackAgentConnection.java
@@ -30,6 +30,8 @@
 import android.telephony.data.NrQosSessionAttributes;
 import android.util.Log;
 
+import com.android.modules.utils.build.SdkLevel;
+
 import java.util.Objects;
 
 /**
@@ -149,6 +151,7 @@
 
     void sendEventEpsQosSessionAvailable(final QosSession session,
             final EpsBearerQosSessionAttributes attributes) {
+        if (!validateOrSendErrorAndUnregister()) return;
         try {
             if (DBG) log("sendEventEpsQosSessionAvailable: sending...");
             mCallback.onQosEpsBearerSessionAvailable(session, attributes);
@@ -159,6 +162,7 @@
 
     void sendEventNrQosSessionAvailable(final QosSession session,
             final NrQosSessionAttributes attributes) {
+        if (!validateOrSendErrorAndUnregister()) return;
         try {
             if (DBG) log("sendEventNrQosSessionAvailable: sending...");
             mCallback.onNrQosSessionAvailable(session, attributes);
@@ -168,6 +172,7 @@
     }
 
     void sendEventQosSessionLost(@NonNull final QosSession session) {
+        if (!validateOrSendErrorAndUnregister()) return;
         try {
             if (DBG) log("sendEventQosSessionLost: sending...");
             mCallback.onQosSessionLost(session);
@@ -185,6 +190,21 @@
         }
     }
 
+    private boolean validateOrSendErrorAndUnregister() {
+        final int exceptionType = mFilter.validate();
+        if (exceptionType != EX_TYPE_FILTER_NONE) {
+             log("validation fail before sending QosCallback.");
+             // Error callback is returned from Android T to prevent any disruption of application
+             // running on Android S.
+             if (SdkLevel.isAtLeastT()) {
+                sendEventQosCallbackError(exceptionType);
+                mQosCallbackTracker.unregisterCallback(mCallback);
+            }
+            return false;
+        }
+        return true;
+    }
+
     private static void log(@NonNull final String msg) {
         Log.d(TAG, msg);
     }
diff --git a/tests/common/java/android/net/EthernetNetworkManagementExceptionTest.java b/tests/common/java/android/net/EthernetNetworkManagementExceptionTest.java
new file mode 100644
index 0000000..84b6e54
--- /dev/null
+++ b/tests/common/java/android/net/EthernetNetworkManagementExceptionTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+public class EthernetNetworkManagementExceptionTest {
+    private static final String ERROR_MESSAGE = "Test error message";
+
+    @Test
+    public void testEthernetNetworkManagementExceptionParcelable() {
+        final EthernetNetworkManagementException e =
+                new EthernetNetworkManagementException(ERROR_MESSAGE);
+
+        assertParcelingIsLossless(e);
+    }
+
+    @Test
+    public void testEthernetNetworkManagementExceptionHasExpectedErrorMessage() {
+        final EthernetNetworkManagementException e =
+                new EthernetNetworkManagementException(ERROR_MESSAGE);
+
+        assertEquals(ERROR_MESSAGE, e.getMessage());
+    }
+}
diff --git a/tests/common/java/android/net/LinkPropertiesTest.java b/tests/common/java/android/net/LinkPropertiesTest.java
index 8fc636a..b66a979 100644
--- a/tests/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/common/java/android/net/LinkPropertiesTest.java
@@ -1261,6 +1261,18 @@
         assertFalse(lp.hasIpv4UnreachableDefaultRoute());
     }
 
+    @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    @EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
+    public void testHasExcludeRoute() {
+        LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName("tun0");
+        lp.addRoute(new RouteInfo(new IpPrefix(ADDRV4, 24), RTN_UNICAST));
+        lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 0), RTN_UNICAST));
+        assertFalse(lp.hasExcludeRoute());
+        lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 32), RTN_THROW));
+        assertTrue(lp.hasExcludeRoute());
+    }
+
     @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
     @EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
     public void testRouteAddWithSameKey() throws Exception {
diff --git a/tests/common/java/android/net/netstats/NetworkStatsCollectionTest.kt b/tests/common/java/android/net/netstats/NetworkStatsCollectionTest.kt
index ca0e5ed..368a519 100644
--- a/tests/common/java/android/net/netstats/NetworkStatsCollectionTest.kt
+++ b/tests/common/java/android/net/netstats/NetworkStatsCollectionTest.kt
@@ -16,7 +16,7 @@
 
 package android.net.netstats
 
-import android.net.NetworkIdentitySet
+import android.net.NetworkIdentity
 import android.net.NetworkStatsCollection
 import android.net.NetworkStatsHistory
 import androidx.test.filters.SmallTest
@@ -40,7 +40,7 @@
 
     @Test
     fun testBuilder() {
-        val ident = NetworkIdentitySet()
+        val ident = setOf<NetworkIdentity>()
         val key1 = NetworkStatsCollection.Key(ident, /* uid */ 0, /* set */ 0, /* tag */ 0)
         val key2 = NetworkStatsCollection.Key(ident, /* uid */ 1, /* set */ 0, /* tag */ 0)
         val bucketDuration = 10L
@@ -63,4 +63,4 @@
         val actualHistory = actualEntries[key1] ?: fail("There should be an entry for $key1")
         assertEquals(history1.entries, actualHistory.entries)
     }
-}
\ No newline at end of file
+}
diff --git a/tests/common/java/android/net/netstats/NetworkTemplateTest.kt b/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
new file mode 100644
index 0000000..192694b
--- /dev/null
+++ b/tests/common/java/android/net/netstats/NetworkTemplateTest.kt
@@ -0,0 +1,205 @@
+/*
+ * 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.netstats
+
+import android.net.NetworkStats.DEFAULT_NETWORK_ALL
+import android.net.NetworkStats.METERED_ALL
+import android.net.NetworkStats.METERED_YES
+import android.net.NetworkStats.ROAMING_YES
+import android.net.NetworkStats.ROAMING_ALL
+import android.net.NetworkTemplate
+import android.net.NetworkTemplate.MATCH_BLUETOOTH
+import android.net.NetworkTemplate.MATCH_CARRIER
+import android.net.NetworkTemplate.MATCH_ETHERNET
+import android.net.NetworkTemplate.MATCH_MOBILE
+import android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD
+import android.net.NetworkTemplate.MATCH_PROXY
+import android.net.NetworkTemplate.MATCH_WIFI
+import android.net.NetworkTemplate.MATCH_WIFI_WILDCARD
+import android.net.NetworkTemplate.NETWORK_TYPE_ALL
+import android.net.NetworkTemplate.OEM_MANAGED_ALL
+import android.telephony.TelephonyManager
+import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL
+import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.SC_V2
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+private const val TEST_IMSI1 = "imsi"
+private const val TEST_WIFI_KEY1 = "wifiKey1"
+private const val TEST_WIFI_KEY2 = "wifiKey2"
+
+@RunWith(JUnit4::class)
+@ConnectivityModuleTest
+class NetworkTemplateTest {
+    @Rule
+    @JvmField
+    val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = SC_V2)
+
+    @Test
+    fun testBuilderMatchRules() {
+        // Verify unknown match rules cannot construct templates.
+        listOf(Integer.MIN_VALUE, -1, Integer.MAX_VALUE).forEach {
+            assertFailsWith<IllegalArgumentException> {
+                NetworkTemplate.Builder(it).build()
+            }
+        }
+
+        // Verify hidden match rules cannot construct templates.
+        listOf(MATCH_WIFI_WILDCARD, MATCH_MOBILE_WILDCARD, MATCH_PROXY).forEach {
+            assertFailsWith<IllegalArgumentException> {
+                NetworkTemplate.Builder(it).build()
+            }
+        }
+
+        // Verify template which matches metered cellular and carrier networks with
+        // the given IMSI. See buildTemplateMobileAll and buildTemplateCarrierMetered.
+        listOf(MATCH_MOBILE, MATCH_CARRIER).forEach { matchRule ->
+            NetworkTemplate.Builder(matchRule).setSubscriberIds(setOf(TEST_IMSI1))
+                    .setMeteredness(METERED_YES).build().let {
+                        val expectedTemplate = NetworkTemplate(matchRule, TEST_IMSI1,
+                                arrayOf(TEST_IMSI1), arrayOf<String>(), METERED_YES,
+                                ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+                                OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+                        assertEquals(expectedTemplate, it)
+                    }
+        }
+
+        // Verify template which matches roaming cellular and carrier networks with
+        // the given IMSI.
+        listOf(MATCH_MOBILE, MATCH_CARRIER).forEach { matchRule ->
+            NetworkTemplate.Builder(matchRule).setSubscriberIds(setOf(TEST_IMSI1))
+                    .setRoaming(ROAMING_YES).setMeteredness(METERED_YES).build().let {
+                        val expectedTemplate = NetworkTemplate(matchRule, TEST_IMSI1,
+                                arrayOf(TEST_IMSI1), arrayOf<String>(), METERED_YES,
+                                ROAMING_YES, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+                                OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+                        assertEquals(expectedTemplate, it)
+                    }
+        }
+
+        // Verify carrier template cannot be created without IMSI.
+        assertFailsWith<IllegalArgumentException> {
+            NetworkTemplate.Builder(MATCH_CARRIER).build()
+        }
+
+        // Verify template which matches metered cellular networks,
+        // regardless of IMSI. See buildTemplateMobileWildcard.
+        NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES).build().let {
+            val expectedTemplate = NetworkTemplate(MATCH_MOBILE_WILDCARD, null /*subscriberId*/,
+                    null /*subscriberIds*/, arrayOf<String>(),
+                    METERED_YES, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+                    OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+            assertEquals(expectedTemplate, it)
+        }
+
+        // Verify template which matches metered cellular networks and ratType.
+        // See NetworkTemplate#buildTemplateMobileWithRatType.
+        NetworkTemplate.Builder(MATCH_MOBILE).setSubscriberIds(setOf(TEST_IMSI1))
+                .setMeteredness(METERED_YES).setRatType(TelephonyManager.NETWORK_TYPE_UMTS)
+                .build().let {
+                    val expectedTemplate = NetworkTemplate(MATCH_MOBILE, TEST_IMSI1,
+                            arrayOf(TEST_IMSI1), arrayOf<String>(), METERED_YES,
+                            ROAMING_ALL, DEFAULT_NETWORK_ALL, TelephonyManager.NETWORK_TYPE_UMTS,
+                            OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+                    assertEquals(expectedTemplate, it)
+                }
+
+        // Verify template which matches all wifi networks,
+        // regardless of Wifi Network Key. See buildTemplateWifiWildcard and buildTemplateWifi.
+        NetworkTemplate.Builder(MATCH_WIFI).build().let {
+            val expectedTemplate = NetworkTemplate(MATCH_WIFI_WILDCARD, null /*subscriberId*/,
+                    null /*subscriberIds*/, arrayOf<String>(),
+                    METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+                    OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+            assertEquals(expectedTemplate, it)
+        }
+
+        // Verify template which matches wifi networks with the given Wifi Network Key.
+        // See buildTemplateWifi(wifiNetworkKey).
+        NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build().let {
+            val expectedTemplate = NetworkTemplate(MATCH_WIFI, null /*subscriberId*/,
+                    null /*subscriberIds*/, arrayOf(TEST_WIFI_KEY1),
+                    METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+                    OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+            assertEquals(expectedTemplate, it)
+        }
+
+        // Verify template which matches all wifi networks with the
+        // given Wifi Network Key, and IMSI. See buildTemplateWifi(wifiNetworkKey, subscriberId).
+        NetworkTemplate.Builder(MATCH_WIFI).setSubscriberIds(setOf(TEST_IMSI1))
+                .setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build().let {
+                    val expectedTemplate = NetworkTemplate(MATCH_WIFI, TEST_IMSI1,
+                            arrayOf(TEST_IMSI1), arrayOf(TEST_WIFI_KEY1),
+                            METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+                            OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
+                    assertEquals(expectedTemplate, it)
+                }
+
+        // Verify template which matches ethernet and bluetooth networks.
+        // See buildTemplateEthernet and buildTemplateBluetooth.
+        listOf(MATCH_ETHERNET, MATCH_BLUETOOTH).forEach { matchRule ->
+            NetworkTemplate.Builder(matchRule).build().let {
+                val expectedTemplate = NetworkTemplate(matchRule, null /*subscriberId*/,
+                        null /*subscriberIds*/, arrayOf<String>(),
+                        METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+                        OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+                assertEquals(expectedTemplate, it)
+            }
+        }
+    }
+
+    @Test
+    fun testBuilderWifiNetworkKeys() {
+        // Verify template builder which generates same template with the given different
+        // sequence keys.
+        NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(
+                setOf(TEST_WIFI_KEY1, TEST_WIFI_KEY2)).build().let {
+            val expectedTemplate = NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(
+                    setOf(TEST_WIFI_KEY2, TEST_WIFI_KEY1)).build()
+            assertEquals(expectedTemplate, it)
+        }
+
+        // Verify template which matches non-wifi networks with the given key is invalid.
+        listOf(MATCH_MOBILE, MATCH_CARRIER, MATCH_ETHERNET, MATCH_BLUETOOTH, -1,
+                Integer.MAX_VALUE).forEach { matchRule ->
+            assertFailsWith<IllegalArgumentException> {
+                NetworkTemplate.Builder(matchRule).setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build()
+            }
+        }
+
+        // Verify template which matches wifi networks with the given null key is invalid.
+        assertFailsWith<IllegalArgumentException> {
+            NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf(null)).build()
+        }
+
+        // Verify template which matches wifi wildcard with the given empty key set.
+        NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf<String>()).build().let {
+            val expectedTemplate = NetworkTemplate(MATCH_WIFI_WILDCARD, null /*subscriberId*/,
+                    arrayOf<String>() /*subscriberIds*/, arrayOf<String>(),
+                    METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
+                    OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
+            assertEquals(expectedTemplate, it)
+        }
+    }
+}
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index 47ea53e..ac84e57 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -26,6 +26,7 @@
         "tradefed",
     ],
     static_libs: [
+        "CompatChangeGatingTestBase",
         "modules-utils-build-testing",
     ],
     // Tag this module as a cts test artifact
@@ -37,6 +38,8 @@
     data: [
         ":CtsHostsideNetworkTestsApp",
         ":CtsHostsideNetworkTestsApp2",
+        ":CtsHostsideNetworkTestsApp3",
+        ":CtsHostsideNetworkTestsApp3PreT",
         ":CtsHostsideNetworkTestsAppNext",
     ],
     per_testcase_directory: true,
diff --git a/tests/cts/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
index 01c8cd2..edfaf9f 100644
--- a/tests/cts/hostside/app2/Android.bp
+++ b/tests/cts/hostside/app2/Android.bp
@@ -23,6 +23,7 @@
     defaults: ["cts_support_defaults"],
     sdk_version: "test_current",
     static_libs: [
+        "androidx.annotation_annotation",
         "CtsHostsideNetworkTestsAidl",
         "NetworkStackApiStableShims",
     ],
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
index 08cdea7..51acfdf 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
@@ -30,6 +30,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import androidx.annotation.GuardedBy;
+
 import com.android.cts.net.hostside.INetworkStateObserver;
 
 /**
@@ -37,6 +39,7 @@
  */
 public class MyActivity extends Activity {
 
+    @GuardedBy("this")
     private BroadcastReceiver finishCommandReceiver = null;
 
     @Override
@@ -47,8 +50,11 @@
 
     @Override
     public void finish() {
-        if (finishCommandReceiver != null) {
-            unregisterReceiver(finishCommandReceiver);
+        synchronized (this) {
+            if (finishCommandReceiver != null) {
+                unregisterReceiver(finishCommandReceiver);
+                finishCommandReceiver = null;
+            }
         }
         super.finish();
     }
@@ -71,14 +77,17 @@
         super.onResume();
         Log.d(TAG, "MyActivity.onResume(): " + getIntent());
         Common.notifyNetworkStateObserver(this, getIntent(), TYPE_COMPONENT_ACTIVTY);
-        finishCommandReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                Log.d(TAG, "Finishing MyActivity");
-                MyActivity.this.finish();
-            }
-        };
-        registerReceiver(finishCommandReceiver, new IntentFilter(ACTION_FINISH_ACTIVITY));
+        synchronized (this) {
+            finishCommandReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    Log.d(TAG, "Finishing MyActivity");
+                    MyActivity.this.finish();
+                }
+            };
+            registerReceiver(finishCommandReceiver, new IntentFilter(ACTION_FINISH_ACTIVITY),
+                    Context.RECEIVER_EXPORTED);
+        }
     }
 
     @Override
diff --git a/tests/cts/hostside/app3/Android.bp b/tests/cts/hostside/app3/Android.bp
new file mode 100644
index 0000000..141cf03
--- /dev/null
+++ b/tests/cts/hostside/app3/Android.bp
@@ -0,0 +1,54 @@
+//
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_defaults {
+    name: "CtsHostsideNetworkTestsApp3Defaults",
+    srcs: ["src/**/*.java"],
+    libs: [
+        "junit",
+    ],
+    static_libs: [
+        "ctstestrunner-axt",
+        "truth-prebuilt",
+    ],
+
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsHostsideNetworkTestsApp3",
+    defaults: [
+        "cts_support_defaults",
+        "CtsHostsideNetworkTestsApp3Defaults",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsHostsideNetworkTestsApp3PreT",
+    target_sdk_version: "31",
+    defaults: [
+        "cts_support_defaults",
+        "CtsHostsideNetworkTestsApp3Defaults",
+    ],
+}
diff --git a/tests/cts/hostside/app3/AndroidManifest.xml b/tests/cts/hostside/app3/AndroidManifest.xml
new file mode 100644
index 0000000..eabcacb
--- /dev/null
+++ b/tests/cts/hostside/app3/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.cts.net.hostside.app3">
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.net.hostside.app3" />
+
+</manifest>
diff --git a/tests/cts/hostside/app3/src/com/android/cts/net/hostside/app3/ExcludedRoutesGatingTest.java b/tests/cts/hostside/app3/src/com/android/cts/net/hostside/app3/ExcludedRoutesGatingTest.java
new file mode 100644
index 0000000..a1a8209
--- /dev/null
+++ b/tests/cts/hostside/app3/src/com/android/cts/net/hostside/app3/ExcludedRoutesGatingTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net.hostside.app3;
+
+import static org.junit.Assert.assertEquals;
+
+import android.Manifest;
+import android.net.IpPrefix;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests to verify {@link LinkProperties#getRoutes} behavior, depending on
+ * {@LinkProperties#EXCLUDED_ROUTES} change state.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExcludedRoutesGatingTest {
+    @Before
+    public void setUp() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
+                        Manifest.permission.READ_COMPAT_CHANGE_CONFIG);
+    }
+
+    @After
+    public void tearDown() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testExcludedRoutesChangeEnabled() {
+        final LinkProperties lp = makeLinkPropertiesWithExcludedRoutes();
+
+        // Excluded routes change is enabled: non-RTN_UNICAST routes are visible.
+        assertEquals(2, lp.getRoutes().size());
+        assertEquals(2, lp.getAllRoutes().size());
+    }
+
+    @Test
+    public void testExcludedRoutesChangeDisabled() {
+        final LinkProperties lp = makeLinkPropertiesWithExcludedRoutes();
+
+        // Excluded routes change is disabled: non-RTN_UNICAST routes are filtered out.
+        assertEquals(0, lp.getRoutes().size());
+        assertEquals(0, lp.getAllRoutes().size());
+    }
+
+    private LinkProperties makeLinkPropertiesWithExcludedRoutes() {
+        final LinkProperties lp = new LinkProperties();
+
+        lp.addRoute(new RouteInfo(new IpPrefix("10.0.0.0/8"), null, null, RouteInfo.RTN_THROW));
+        lp.addRoute(new RouteInfo(new IpPrefix("2001:db8::/64"), null, null,
+                RouteInfo.RTN_UNREACHABLE));
+
+        return lp;
+    }
+}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideLinkPropertiesGatingTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideLinkPropertiesGatingTests.java
new file mode 100644
index 0000000..b65fb6b
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideLinkPropertiesGatingTests.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net;
+
+import android.compat.cts.CompatChangeGatingTestCase;
+
+import java.util.Set;
+
+/**
+ * Tests for the {@link android.net.LinkProperties#EXCLUDED_ROUTES} compatibility change.
+ */
+public class HostsideLinkPropertiesGatingTests extends CompatChangeGatingTestCase {
+    private static final String TEST_APK = "CtsHostsideNetworkTestsApp3.apk";
+    private static final String TEST_APK_PRE_T = "CtsHostsideNetworkTestsApp3PreT.apk";
+    private static final String TEST_PKG = "com.android.cts.net.hostside.app3";
+    private static final String TEST_CLASS = ".ExcludedRoutesGatingTest";
+
+    private static final long EXCLUDED_ROUTES_CHANGE_ID = 186082280;
+
+    protected void tearDown() throws Exception {
+        uninstallPackage(TEST_PKG, true);
+    }
+
+    public void testExcludedRoutesChangeEnabled() throws Exception {
+        installPackage(TEST_APK, true);
+        runDeviceCompatTest("testExcludedRoutesChangeEnabled");
+    }
+
+    public void testExcludedRoutesChangeDisabledPreT() throws Exception {
+        installPackage(TEST_APK_PRE_T, true);
+        runDeviceCompatTest("testExcludedRoutesChangeDisabled");
+    }
+
+    public void testExcludedRoutesChangeDisabledByOverride() throws Exception {
+        installPackage(TEST_APK, true);
+        runDeviceCompatTestWithChangeDisabled("testExcludedRoutesChangeDisabled");
+    }
+
+    public void testExcludedRoutesChangeEnabledByOverridePreT() throws Exception {
+        installPackage(TEST_APK_PRE_T, true);
+        runDeviceCompatTestWithChangeEnabled("testExcludedRoutesChangeEnabled");
+    }
+
+    private void runDeviceCompatTest(String methodName) throws Exception {
+        runDeviceCompatTest(TEST_PKG, TEST_CLASS, methodName, Set.of(), Set.of());
+    }
+
+    private void runDeviceCompatTestWithChangeEnabled(String methodName) throws Exception {
+        runDeviceCompatTest(TEST_PKG, TEST_CLASS, methodName, Set.of(EXCLUDED_ROUTES_CHANGE_ID),
+                Set.of());
+    }
+
+    private void runDeviceCompatTestWithChangeDisabled(String methodName) throws Exception {
+        runDeviceCompatTest(TEST_PKG, TEST_CLASS, methodName, Set.of(),
+                Set.of(EXCLUDED_ROUTES_CHANGE_ID));
+    }
+}
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 48a1c79..33f3af5 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -38,4 +38,20 @@
         <option name="hidden-api-checks" value="false" />
         <option name="isolated-storage" value="false" />
     </test>
+    <!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
+        one of the Mainline modules below is present on the device used for testing. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <!-- Tethering Module (internal version). -->
+        <option name="mainline-module-package-name" value="com.google.android.tethering" />
+        <!-- Tethering Module (AOSP version). -->
+        <option name="mainline-module-package-name" value="com.android.tethering" />
+        <!-- NetworkStack Module (internal version). Should always be installed with CaptivePortalLogin. -->
+        <option name="mainline-module-package-name" value="com.google.android.networkstack" />
+        <!-- NetworkStack Module (AOSP version). Should always be installed with CaptivePortalLogin. -->
+        <option name="mainline-module-package-name" value="com.android.networkstack" />
+        <!-- Resolver Module (internal version). -->
+        <option name="mainline-module-package-name" value="com.google.android.resolv" />
+        <!-- Resolver Module (AOSP version). -->
+        <option name="mainline-module-package-name" value="com.android.resolv" />
+    </object>
 </configuration>
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index a129108..8f6786f 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -52,6 +52,7 @@
 import static android.net.ConnectivityManager.TYPE_PROXY;
 import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
+import static android.net.ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
@@ -164,7 +165,6 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
-import android.util.Pair;
 import android.util.Range;
 
 import androidx.test.InstrumentationRegistry;
@@ -182,6 +182,7 @@
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.DevSdkIgnoreRuleKt;
+import com.android.testutils.DeviceInfoUtils;
 import com.android.testutils.DumpTestUtils;
 import com.android.testutils.RecorderCallback.CallbackEntry;
 import com.android.testutils.TestHttpServer;
@@ -1605,51 +1606,7 @@
 
     private static boolean isTcpKeepaliveSupportedByKernel() {
         final String kVersionString = VintfRuntimeInfo.getKernelVersion();
-        return compareMajorMinorVersion(kVersionString, "4.8") >= 0;
-    }
-
-    private static Pair<Integer, Integer> getVersionFromString(String version) {
-        // Only gets major and minor number of the version string.
-        final Pattern versionPattern = Pattern.compile("^(\\d+)(\\.(\\d+))?.*");
-        final Matcher m = versionPattern.matcher(version);
-        if (m.matches()) {
-            final int major = Integer.parseInt(m.group(1));
-            final int minor = TextUtils.isEmpty(m.group(3)) ? 0 : Integer.parseInt(m.group(3));
-            return new Pair<>(major, minor);
-        } else {
-            return new Pair<>(0, 0);
-        }
-    }
-
-    // TODO: Move to util class.
-    private static int compareMajorMinorVersion(final String s1, final String s2) {
-        final Pair<Integer, Integer> v1 = getVersionFromString(s1);
-        final Pair<Integer, Integer> v2 = getVersionFromString(s2);
-
-        if (v1.first == v2.first) {
-            return Integer.compare(v1.second, v2.second);
-        } else {
-            return Integer.compare(v1.first, v2.first);
-        }
-    }
-
-    /**
-     * Verifies that version string compare logic returns expected result for various cases.
-     * Note that only major and minor number are compared.
-     */
-    @Test
-    public void testMajorMinorVersionCompare() {
-        assertEquals(0, compareMajorMinorVersion("4.8.1", "4.8"));
-        assertEquals(1, compareMajorMinorVersion("4.9", "4.8.1"));
-        assertEquals(1, compareMajorMinorVersion("5.0", "4.8"));
-        assertEquals(1, compareMajorMinorVersion("5", "4.8"));
-        assertEquals(0, compareMajorMinorVersion("5", "5.0"));
-        assertEquals(1, compareMajorMinorVersion("5-beta1", "4.8"));
-        assertEquals(0, compareMajorMinorVersion("4.8.0.0", "4.8"));
-        assertEquals(0, compareMajorMinorVersion("4.8-RC1", "4.8"));
-        assertEquals(0, compareMajorMinorVersion("4.8", "4.8"));
-        assertEquals(-1, compareMajorMinorVersion("3.10", "4.8.0"));
-        assertEquals(-1, compareMajorMinorVersion("4.7.10.10", "4.8"));
+        return DeviceInfoUtils.compareMajorMinorVersion(kVersionString, "4.8") >= 0;
     }
 
     /**
@@ -3212,7 +3169,7 @@
     @AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
     @Test
     public void testUidsAllowedOnRestrictedNetworks() throws Exception {
-        assumeTrue(TestUtils.shouldTestSApis());
+        assumeTestSApis();
 
         // TODO (b/175199465): figure out a reasonable permission check for
         //  setUidsAllowedOnRestrictedNetworks that allows tests but not system-external callers.
@@ -3225,10 +3182,10 @@
         // because it has been just installed to device. In case the uid is existed in setting
         // mistakenly, try to remove the uid and set correct uids to setting.
         originalUidsAllowedOnRestrictedNetworks.remove(uid);
-        runWithShellPermissionIdentity(() ->
-                ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(
-                        mContext, originalUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
+        runWithShellPermissionIdentity(() -> setUidsAllowedOnRestrictedNetworks(
+                mContext, originalUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
 
+        // File a restricted network request with permission first to hold the connection.
         final TestableNetworkCallback testNetworkCb = new TestableNetworkCallback();
         final NetworkRequest testRequest = new NetworkRequest.Builder()
                 .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
@@ -3240,6 +3197,19 @@
         runWithShellPermissionIdentity(() -> requestNetwork(testRequest, testNetworkCb),
                 CONNECTIVITY_USE_RESTRICTED_NETWORKS);
 
+        // File another restricted network request without permission.
+        final TestableNetworkCallback restrictedNetworkCb = new TestableNetworkCallback();
+        final NetworkRequest restrictedRequest = new NetworkRequest.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .setNetworkSpecifier(CompatUtil.makeTestNetworkSpecifier(
+                        TEST_RESTRICTED_NW_IFACE_NAME))
+                .build();
+        // Uid is not in allowed list and no permissions. Expect that SecurityException will throw.
+        assertThrows(SecurityException.class,
+                () -> mCm.requestNetwork(restrictedRequest, restrictedNetworkCb));
+
         final NetworkAgent agent = createRestrictedNetworkAgent(mContext);
         final Network network = agent.getNetwork();
 
@@ -3259,19 +3229,26 @@
             final Set<Integer> newUidsAllowedOnRestrictedNetworks =
                     new ArraySet<>(originalUidsAllowedOnRestrictedNetworks);
             newUidsAllowedOnRestrictedNetworks.add(uid);
-            runWithShellPermissionIdentity(() ->
-                    ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(
-                            mContext, newUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
+            runWithShellPermissionIdentity(() -> setUidsAllowedOnRestrictedNetworks(
+                    mContext, newUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
             // Wait a while for sending allowed uids on the restricted network to netd.
-            // TODD: Have a significant signal to know the uids has been send to netd.
+            // TODD: Have a significant signal to know the uids has been sent to netd.
             assertBindSocketToNetworkSuccess(network);
+
+            // Uid is in allowed list. Try file network request again.
+            requestNetwork(restrictedRequest, restrictedNetworkCb);
+            // Verify that the network is restricted.
+            restrictedNetworkCb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
+                    NETWORK_CALLBACK_TIMEOUT_MS,
+                    entry -> network.equals(entry.getNetwork())
+                            && (!((CallbackEntry.CapabilitiesChanged) entry).getCaps()
+                            .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)));
         } finally {
             agent.unregister();
 
             // Restore setting.
-            runWithShellPermissionIdentity(() ->
-                    ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(
-                            mContext, originalUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
+            runWithShellPermissionIdentity(() -> setUidsAllowedOnRestrictedNetworks(
+                    mContext, originalUidsAllowedOnRestrictedNetworks), NETWORK_SETTINGS);
         }
     }
 
@@ -3295,6 +3272,12 @@
         assertTrue(dumpOutput, dumpOutput.contains("BPF map content"));
     }
 
+    private void assumeTestSApis() {
+        // Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
+        // shims, and @IgnoreUpTo does not check that.
+        assumeTrue(TestUtils.shouldTestSApis());
+    }
+
     private void unregisterRegisteredCallbacks() {
         for (NetworkCallback callback: mRegisteredCallbacks) {
             mCm.unregisterNetworkCallback(callback);
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
index fb720a7..f86c5cd 100644
--- a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -29,8 +29,20 @@
 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 static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG;
+import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT;
+
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.app.AppOpsManager;
+import android.app.Instrumentation;
 import android.app.usage.NetworkStats;
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
@@ -40,7 +52,11 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkRequest;
+import android.net.NetworkStatsCollection;
+import android.net.NetworkStatsHistory;
 import android.net.TrafficStats;
+import android.net.netstats.NetworkStatsDataMigrationUtils;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Process;
@@ -48,11 +64,23 @@
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
 import android.telephony.TelephonyManager;
-import android.test.InstrumentationTestCase;
+import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.testutils.ConnectivityModuleTest;
+import com.android.testutils.DevSdkIgnoreRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -62,8 +90,18 @@
 import java.net.UnknownHostException;
 import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
-public class NetworkStatsManagerTest extends InstrumentationTestCase {
+@ConnectivityModuleTest
+@AppModeFull(reason = "instant apps cannot be granted USAGE_STATS")
+@RunWith(AndroidJUnit4.class)
+public class NetworkStatsManagerTest {
+    @Rule
+    public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(Build.VERSION_CODES.Q);
+
     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}";
@@ -164,9 +202,11 @@
             };
 
     private String mPkg;
+    private Context mContext;
     private NetworkStatsManager mNsm;
     private ConnectivityManager mCm;
     private PackageManager mPm;
+    private Instrumentation mInstrumentation;
     private long mStartTime;
     private long mEndTime;
 
@@ -224,44 +264,40 @@
         }
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mNsm = (NetworkStatsManager) getInstrumentation().getContext()
-                .getSystemService(Context.NETWORK_STATS_SERVICE);
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mNsm = mContext.getSystemService(NetworkStatsManager.class);
         mNsm.setPollForce(true);
 
-        mCm = (ConnectivityManager) getInstrumentation().getContext()
-                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        mCm = mContext.getSystemService(ConnectivityManager.class);
+        mPm = mContext.getPackageManager();
+        mPkg = mContext.getPackageName();
 
-        mPm = getInstrumentation().getContext().getPackageManager();
-
-        mPkg = getInstrumentation().getContext().getPackageName();
-
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
         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 {
+    @After
+    public 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);
+        SystemUtil.runShellCommand(mInstrumentation, command);
     }
 
     private String getAppOpsMode(String appop) throws Exception {
         final String command = MessageFormat.format(APPOPS_GET_SHELL_COMMAND, mPkg, appop);
-        String result = SystemUtil.runShellCommand(command);
+        String result = SystemUtil.runShellCommand(mInstrumentation, command);
         if (result == null) {
             Log.w(LOG_TAG, "App op " + appop + " could not be read.");
         }
@@ -269,7 +305,7 @@
     }
 
     private boolean isInForeground() throws IOException {
-        String result = SystemUtil.runShellCommand(getInstrumentation(),
+        String result = SystemUtil.runShellCommand(mInstrumentation,
                 "cmd activity get-uid-state " + Process.myUid());
         return result.contains("FOREGROUND");
     }
@@ -366,15 +402,14 @@
     private String getSubscriberId(int networkIndex) {
         int networkType = mNetworkInterfacesToTest[networkIndex].getNetworkType();
         if (ConnectivityManager.TYPE_MOBILE == networkType) {
-            TelephonyManager tm = (TelephonyManager) getInstrumentation().getContext()
-                    .getSystemService(Context.TELEPHONY_SERVICE);
+            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
             return ShellIdentityUtils.invokeMethodWithShellPermissions(tm,
                     (telephonyManager) -> telephonyManager.getSubscriberId());
         }
         return "";
     }
 
-    @AppModeFull
+    @Test
     public void testDeviceSummary() throws Exception {
         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
             if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
@@ -410,7 +445,7 @@
         }
     }
 
-    @AppModeFull
+    @Test
     public void testUserSummary() throws Exception {
         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
             if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
@@ -446,7 +481,7 @@
         }
     }
 
-    @AppModeFull
+    @Test
     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
@@ -522,7 +557,7 @@
         }
     }
 
-    @AppModeFull
+    @Test
     public void testAppDetails() throws Exception {
         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
             // Relatively large tolerance to accommodate for history bucket size.
@@ -565,7 +600,7 @@
         }
     }
 
-    @AppModeFull
+    @Test
     public void testUidDetails() throws Exception {
         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
             // Relatively large tolerance to accommodate for history bucket size.
@@ -619,7 +654,7 @@
         }
     }
 
-    @AppModeFull
+    @Test
     public void testTagDetails() throws Exception {
         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
             // Relatively large tolerance to accommodate for history bucket size.
@@ -726,7 +761,7 @@
                 bucket.getRxBytes(), bucket.getTxBytes()));
     }
 
-    @AppModeFull
+    @Test
     public void testUidTagStateDetails() throws Exception {
         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
             // Relatively large tolerance to accommodate for history bucket size.
@@ -803,7 +838,7 @@
         }
     }
 
-    @AppModeFull
+    @Test
     public void testCallback() throws Exception {
         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
             // Relatively large tolerance to accommodate for history bucket size.
@@ -825,6 +860,43 @@
             // storing files of >2MB in CTS.
 
             mNsm.unregisterUsageCallback(usageCallback);
+
+            // For T- devices, the registerUsageCallback invocation below will need a looper
+            // from the thread that calls into the API, which is not available in the test.
+            if (SdkLevel.isAtLeastT()) {
+                mNsm.registerUsageCallback(mNetworkInterfacesToTest[i].getNetworkType(),
+                        getSubscriberId(i), THRESHOLD_BYTES, usageCallback);
+                mNsm.unregisterUsageCallback(usageCallback);
+            }
+        }
+    }
+
+    @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+    @Test
+    public void testDataMigrationUtils() throws Exception {
+        final List<String> prefixes = List.of(PREFIX_UID, PREFIX_XT, PREFIX_UID_TAG);
+        for (final String prefix : prefixes) {
+            final long duration = TextUtils.equals(PREFIX_XT, prefix) ? TimeUnit.HOURS.toMillis(1)
+                    : TimeUnit.HOURS.toMillis(2);
+
+            final NetworkStatsCollection collection =
+                    NetworkStatsDataMigrationUtils.readPlatformCollection(prefix, duration);
+
+            final long now = System.currentTimeMillis();
+            final Set<Map.Entry<NetworkStatsCollection.Key, NetworkStatsHistory>> entries =
+                    collection.getEntries().entrySet();
+            for (final Map.Entry<NetworkStatsCollection.Key, NetworkStatsHistory> entry : entries) {
+                for (final NetworkStatsHistory.Entry historyEntry : entry.getValue().getEntries()) {
+                    // Verify all value fields are reasonable.
+                    assertTrue(historyEntry.getBucketStart() <= now);
+                    assertTrue(historyEntry.getActiveTime() <= duration);
+                    assertTrue(historyEntry.getRxBytes() >= 0);
+                    assertTrue(historyEntry.getRxPackets() >= 0);
+                    assertTrue(historyEntry.getTxBytes() >= 0);
+                    assertTrue(historyEntry.getTxPackets() >= 0);
+                    assertTrue(historyEntry.getOperations() >= 0);
+                }
+            }
         }
     }
 
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index b139a9b..7b0451f 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -82,6 +82,7 @@
 private const val TAG = "NsdManagerTest"
 private const val SERVICE_TYPE = "_nmt._tcp"
 private const val TIMEOUT_MS = 2000L
+private const val NO_CALLBACK_TIMEOUT_MS = 200L
 private const val DBG = false
 
 private val nsdShim = NsdShimImpl.newInstance()
@@ -136,6 +137,11 @@
                     nextEvent.javaClass.simpleName)
             return nextEvent
         }
+
+        inline fun assertNoCallback(timeoutMs: Long = NO_CALLBACK_TIMEOUT_MS) {
+            val cb = nextEvents.poll(timeoutMs)
+            assertNull(cb, "Expected no callback but got $cb")
+        }
     }
 
     private class NsdRegistrationRecord : RegistrationListener,
@@ -556,6 +562,55 @@
         }
     }
 
+    @Test
+    fun testNsdManager_RegisterOnNetwork() {
+        // This test requires shims supporting T+ APIs (NsdServiceInfo.network)
+        assumeTrue(ConstantsShim.VERSION > SC_V2)
+
+        val si = NsdServiceInfo()
+        si.serviceType = SERVICE_TYPE
+        si.serviceName = this.serviceName
+        si.network = testNetwork1.network
+        si.port = 12345 // Test won't try to connect so port does not matter
+
+        // Register service on testNetwork1
+        val registrationRecord = NsdRegistrationRecord()
+        registerService(registrationRecord, si)
+        val discoveryRecord = NsdDiscoveryRecord()
+        val discoveryRecord2 = NsdDiscoveryRecord()
+        val discoveryRecord3 = NsdDiscoveryRecord()
+
+        tryTest {
+            // Discover service on testNetwork1.
+            nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+                testNetwork1.network, Executor { it.run() }, discoveryRecord)
+            // Expect that service is found on testNetwork1
+            val foundInfo = discoveryRecord.waitForServiceDiscovered(
+                serviceName, testNetwork1.network)
+            assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo))
+
+            // Discover service on testNetwork2.
+            nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+                testNetwork2.network, Executor { it.run() }, discoveryRecord2)
+            // Expect that discovery is started then no other callbacks.
+            discoveryRecord2.expectCallback<DiscoveryStarted>()
+            discoveryRecord2.assertNoCallback()
+
+            // Discover service on all networks (not specify any network).
+            nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+                null as Network? /* network */, Executor { it.run() }, discoveryRecord3)
+            // Expect that service is found on testNetwork1
+            val foundInfo3 = discoveryRecord3.waitForServiceDiscovered(
+                    serviceName, testNetwork1.network)
+            assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo3))
+        } cleanupStep {
+            nsdManager.stopServiceDiscovery(discoveryRecord2)
+            discoveryRecord2.expectCallback<DiscoveryStopped>()
+        } cleanup {
+            nsdManager.unregisterService(registrationRecord)
+        }
+    }
+
     /**
      * Register a service and return its registration record.
      */
diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java
index f324630..c327868 100644
--- a/tests/unit/java/android/net/ConnectivityManagerTest.java
+++ b/tests/unit/java/android/net/ConnectivityManagerTest.java
@@ -41,6 +41,7 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -72,6 +73,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
@@ -82,6 +84,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.lang.ref.WeakReference;
+
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
 @DevSdkIgnoreRule.IgnoreUpTo(VERSION_CODES.R)
@@ -461,4 +465,49 @@
         }
         fail("expected exception of type " + throwableType);
     }
+
+    private static class MockContext extends BroadcastInterceptingContext {
+        MockContext(Context base) {
+            super(base);
+        }
+
+        @Override
+        public Context getApplicationContext() {
+            return mock(Context.class);
+        }
+    }
+
+    private WeakReference<Context> makeConnectivityManagerAndReturnContext() {
+        // Mockito may have an internal reference to the mock, creating MockContext for testing.
+        final Context c = new MockContext(mock(Context.class));
+
+        new ConnectivityManager(c, mService);
+
+        return new WeakReference<>(c);
+    }
+
+    private void forceGC() {
+        // First GC ensures that objects are collected for finalization, then second GC ensures
+        // they're garbage-collected after being finalized.
+        System.gc();
+        System.runFinalization();
+        System.gc();
+    }
+
+    @Test
+    public void testConnectivityManagerDoesNotLeakContext() throws Exception {
+        final WeakReference<Context> ref = makeConnectivityManagerAndReturnContext();
+
+        final int attempts = 100;
+        final long waitIntervalMs = 50;
+        for (int i = 0; i < attempts; i++) {
+            forceGC();
+            if (ref.get() == null) break;
+
+            Thread.sleep(waitIntervalMs);
+        }
+
+        assertNull("ConnectivityManager weak reference still not null after " + attempts
+                    + " attempts", ref.get());
+    }
 }
diff --git a/tests/unit/java/android/net/NetworkStatsCollectionTest.java b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
index 0f02850..b518a61 100644
--- a/tests/unit/java/android/net/NetworkStatsCollectionTest.java
+++ b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
@@ -37,12 +37,15 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
 
+import android.annotation.NonNull;
 import android.content.res.Resources;
+import android.net.NetworkStatsCollection.Key;
 import android.os.Process;
 import android.os.UserHandle;
 import android.telephony.SubscriptionPlan;
 import android.telephony.TelephonyManager;
 import android.text.format.DateUtils;
+import android.util.ArrayMap;
 import android.util.RecurrenceRule;
 
 import androidx.test.InstrumentationRegistry;
@@ -73,6 +76,8 @@
 import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Tests for {@link NetworkStatsCollection}.
@@ -531,6 +536,86 @@
         assertThrows(ArithmeticException.class, () -> multiplySafeByRational(30, 3, 0));
     }
 
+    private static void assertCollectionEntries(
+            @NonNull Map<Key, NetworkStatsHistory> expectedEntries,
+            @NonNull NetworkStatsCollection collection) {
+        final Map<Key, NetworkStatsHistory> actualEntries = collection.getEntries();
+        assertEquals(expectedEntries.size(), actualEntries.size());
+        for (Key expectedKey : expectedEntries.keySet()) {
+            final NetworkStatsHistory expectedHistory = expectedEntries.get(expectedKey);
+            final NetworkStatsHistory actualHistory = actualEntries.get(expectedKey);
+            assertNotNull(actualHistory);
+            assertEquals(expectedHistory.getEntries(), actualHistory.getEntries());
+            actualEntries.remove(expectedKey);
+        }
+        assertEquals(0, actualEntries.size());
+    }
+
+    @Test
+    public void testRemoveHistoryBefore() {
+        final NetworkIdentity testIdent = new NetworkIdentity.Builder()
+                .setSubscriberId(TEST_IMSI).build();
+        final Key key1 = new Key(Set.of(testIdent), 0, 0, 0);
+        final Key key2 = new Key(Set.of(testIdent), 1, 0, 0);
+        final long bucketDuration = 10;
+
+        // Prepare entries for testing, with different bucket start timestamps.
+        final NetworkStatsHistory.Entry entry1 = new NetworkStatsHistory.Entry(10, 10, 40,
+                4, 50, 5, 60);
+        final NetworkStatsHistory.Entry entry2 = new NetworkStatsHistory.Entry(20, 10, 3,
+                41, 7, 1, 0);
+        final NetworkStatsHistory.Entry entry3 = new NetworkStatsHistory.Entry(30, 10, 1,
+                21, 70, 4, 1);
+
+        NetworkStatsHistory history1 = new NetworkStatsHistory.Builder(10, 5)
+                .addEntry(entry1)
+                .addEntry(entry2)
+                .build();
+        NetworkStatsHistory history2 = new NetworkStatsHistory.Builder(10, 5)
+                .addEntry(entry2)
+                .addEntry(entry3)
+                .build();
+        NetworkStatsCollection collection = new NetworkStatsCollection.Builder(bucketDuration)
+                .addEntry(key1, history1)
+                .addEntry(key2, history2)
+                .build();
+
+        // Verify nothing is removed if the cutoff time is equal to bucketStart.
+        collection.removeHistoryBefore(10);
+        final Map<Key, NetworkStatsHistory> expectedEntries = new ArrayMap<>();
+        expectedEntries.put(key1, history1);
+        expectedEntries.put(key2, history2);
+        assertCollectionEntries(expectedEntries, collection);
+
+        // Verify entry1 will be removed if its bucket start before to cutoff timestamp.
+        collection.removeHistoryBefore(11);
+        history1 = new NetworkStatsHistory.Builder(10, 5)
+                .addEntry(entry2)
+                .build();
+        history2 = new NetworkStatsHistory.Builder(10, 5)
+                .addEntry(entry2)
+                .addEntry(entry3)
+                .build();
+        final Map<Key, NetworkStatsHistory> cutoff1Entries1 = new ArrayMap<>();
+        cutoff1Entries1.put(key1, history1);
+        cutoff1Entries1.put(key2, history2);
+        assertCollectionEntries(cutoff1Entries1, collection);
+
+        // Verify entry2 will be removed if its bucket start covers by cutoff timestamp.
+        collection.removeHistoryBefore(22);
+        history2 = new NetworkStatsHistory.Builder(10, 5)
+                .addEntry(entry3)
+                .build();
+        final Map<Key, NetworkStatsHistory> cutoffEntries2 = new ArrayMap<>();
+        // History1 is not expected since the collection will omit empty entries.
+        cutoffEntries2.put(key2, history2);
+        assertCollectionEntries(cutoffEntries2, collection);
+
+        // Verify all entries will be removed if cutoff timestamp covers all.
+        collection.removeHistoryBefore(Long.MAX_VALUE);
+        assertEquals(0, collection.getEntries().size());
+    }
+
     /**
      * Copy a {@link Resources#openRawResource(int)} into {@link File} for
      * testing purposes.
diff --git a/tests/unit/java/android/net/NetworkStatsHistoryTest.java b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
index c5f8c00..26079a2 100644
--- a/tests/unit/java/android/net/NetworkStatsHistoryTest.java
+++ b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
@@ -270,7 +270,7 @@
     }
 
     @Test
-    public void testRemove() throws Exception {
+    public void testRemoveStartingBefore() throws Exception {
         stats = new NetworkStatsHistory(HOUR_IN_MILLIS);
 
         // record some data across 24 buckets
@@ -278,28 +278,28 @@
         assertEquals(24, stats.size());
 
         // try removing invalid data; should be no change
-        stats.removeBucketsBefore(0 - DAY_IN_MILLIS);
+        stats.removeBucketsStartingBefore(0 - DAY_IN_MILLIS);
         assertEquals(24, stats.size());
 
         // try removing far before buckets; should be no change
-        stats.removeBucketsBefore(TEST_START - YEAR_IN_MILLIS);
+        stats.removeBucketsStartingBefore(TEST_START - YEAR_IN_MILLIS);
         assertEquals(24, stats.size());
 
         // try removing just moments into first bucket; should be no change
-        // since that bucket contains data beyond the cutoff
-        stats.removeBucketsBefore(TEST_START + SECOND_IN_MILLIS);
+        // since that bucket doesn't contain data starts before the cutoff
+        stats.removeBucketsStartingBefore(TEST_START);
         assertEquals(24, stats.size());
 
         // try removing single bucket
-        stats.removeBucketsBefore(TEST_START + HOUR_IN_MILLIS);
+        stats.removeBucketsStartingBefore(TEST_START + HOUR_IN_MILLIS);
         assertEquals(23, stats.size());
 
         // try removing multiple buckets
-        stats.removeBucketsBefore(TEST_START + (4 * HOUR_IN_MILLIS));
+        stats.removeBucketsStartingBefore(TEST_START + (4 * HOUR_IN_MILLIS));
         assertEquals(20, stats.size());
 
         // try removing all buckets
-        stats.removeBucketsBefore(TEST_START + YEAR_IN_MILLIS);
+        stats.removeBucketsStartingBefore(TEST_START + YEAR_IN_MILLIS);
         assertEquals(0, stats.size());
     }
 
@@ -349,7 +349,7 @@
                         stats.recordData(start, end, entry);
                     } else {
                         // trim something
-                        stats.removeBucketsBefore(r.nextLong());
+                        stats.removeBucketsStartingBefore(r.nextLong());
                     }
                 }
                 assertConsistent(stats);
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
index 453612f..abd1825 100644
--- a/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -29,12 +29,8 @@
 import android.net.NetworkStats.METERED_NO
 import android.net.NetworkStats.METERED_YES
 import android.net.NetworkStats.ROAMING_ALL
-import android.net.NetworkTemplate.MATCH_BLUETOOTH
-import android.net.NetworkTemplate.MATCH_CARRIER
-import android.net.NetworkTemplate.MATCH_ETHERNET
 import android.net.NetworkTemplate.MATCH_MOBILE
 import android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD
-import android.net.NetworkTemplate.MATCH_PROXY
 import android.net.NetworkTemplate.MATCH_WIFI
 import android.net.NetworkTemplate.MATCH_WIFI_WILDCARD
 import android.net.NetworkTemplate.NETWORK_TYPE_ALL
@@ -52,11 +48,9 @@
 import android.net.wifi.WifiInfo
 import android.os.Build
 import android.telephony.TelephonyManager
-import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_ALL
 import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT
 import com.android.testutils.DevSdkIgnoreRule
 import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.SC_V2
 import com.android.testutils.assertParcelSane
 import org.junit.Before
 import org.junit.Test
@@ -65,7 +59,6 @@
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
 import kotlin.test.assertFalse
 import kotlin.test.assertNotEquals
 import kotlin.test.assertTrue
@@ -555,140 +548,4 @@
             it.assertMatches(identMobileImsi3)
         }
     }
-
-    @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
-    @Test
-    fun testBuilderMatchRules() {
-        // Verify unknown match rules cannot construct templates.
-        listOf(Integer.MIN_VALUE, -1, Integer.MAX_VALUE).forEach {
-            assertFailsWith<IllegalArgumentException> {
-                NetworkTemplate.Builder(it).build()
-            }
-        }
-
-        // Verify hidden match rules cannot construct templates.
-        listOf(MATCH_WIFI_WILDCARD, MATCH_MOBILE_WILDCARD, MATCH_PROXY).forEach {
-            assertFailsWith<IllegalArgumentException> {
-                NetworkTemplate.Builder(it).build()
-            }
-        }
-
-        // Verify template which matches metered cellular and carrier networks with
-        // the given IMSI. See buildTemplateMobileAll and buildTemplateCarrierMetered.
-        listOf(MATCH_MOBILE, MATCH_CARRIER).forEach { matchRule ->
-            NetworkTemplate.Builder(matchRule).setSubscriberIds(setOf(TEST_IMSI1))
-                    .setMeteredness(METERED_YES).build().let {
-                        val expectedTemplate = NetworkTemplate(matchRule, TEST_IMSI1,
-                                arrayOf(TEST_IMSI1), arrayOf<String>(), METERED_YES,
-                                ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
-                                OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
-                        assertEquals(expectedTemplate, it)
-                    }
-        }
-
-        // Verify carrier template cannot be created without IMSI.
-        assertFailsWith<IllegalArgumentException> {
-            NetworkTemplate.Builder(MATCH_CARRIER).build()
-        }
-
-        // Verify template which matches metered cellular networks,
-        // regardless of IMSI. See buildTemplateMobileWildcard.
-        NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES).build().let {
-            val expectedTemplate = NetworkTemplate(MATCH_MOBILE_WILDCARD, null /*subscriberId*/,
-                    null /*subscriberIds*/, arrayOf<String>(),
-                    METERED_YES, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
-                    OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
-            assertEquals(expectedTemplate, it)
-        }
-
-        // Verify template which matches metered cellular networks and ratType.
-        // See NetworkTemplate#buildTemplateMobileWithRatType.
-        NetworkTemplate.Builder(MATCH_MOBILE).setSubscriberIds(setOf(TEST_IMSI1))
-                .setMeteredness(METERED_YES).setRatType(TelephonyManager.NETWORK_TYPE_UMTS)
-                .build().let {
-                    val expectedTemplate = NetworkTemplate(MATCH_MOBILE, TEST_IMSI1,
-                            arrayOf(TEST_IMSI1), arrayOf<String>(), METERED_YES,
-                            ROAMING_ALL, DEFAULT_NETWORK_ALL, TelephonyManager.NETWORK_TYPE_UMTS,
-                            OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
-                    assertEquals(expectedTemplate, it)
-                }
-
-        // Verify template which matches all wifi networks,
-        // regardless of Wifi Network Key. See buildTemplateWifiWildcard and buildTemplateWifi.
-        NetworkTemplate.Builder(MATCH_WIFI).build().let {
-            val expectedTemplate = NetworkTemplate(MATCH_WIFI_WILDCARD, null /*subscriberId*/,
-                    null /*subscriberIds*/, arrayOf<String>(),
-                    METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
-                    OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
-            assertEquals(expectedTemplate, it)
-        }
-
-        // Verify template which matches wifi networks with the given Wifi Network Key.
-        // See buildTemplateWifi(wifiNetworkKey).
-        NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build().let {
-            val expectedTemplate = NetworkTemplate(MATCH_WIFI, null /*subscriberId*/,
-                    null /*subscriberIds*/, arrayOf(TEST_WIFI_KEY1),
-                    METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
-                    OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
-            assertEquals(expectedTemplate, it)
-        }
-
-        // Verify template which matches all wifi networks with the
-        // given Wifi Network Key, and IMSI. See buildTemplateWifi(wifiNetworkKey, subscriberId).
-        NetworkTemplate.Builder(MATCH_WIFI).setSubscriberIds(setOf(TEST_IMSI1))
-                .setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build().let {
-                    val expectedTemplate = NetworkTemplate(MATCH_WIFI, TEST_IMSI1,
-                            arrayOf(TEST_IMSI1), arrayOf(TEST_WIFI_KEY1),
-                            METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
-                            OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_EXACT)
-                    assertEquals(expectedTemplate, it)
-                }
-
-        // Verify template which matches ethernet and bluetooth networks.
-        // See buildTemplateEthernet and buildTemplateBluetooth.
-        listOf(MATCH_ETHERNET, MATCH_BLUETOOTH).forEach { matchRule ->
-            NetworkTemplate.Builder(matchRule).build().let {
-                val expectedTemplate = NetworkTemplate(matchRule, null /*subscriberId*/,
-                        null /*subscriberIds*/, arrayOf<String>(),
-                        METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
-                        OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
-                assertEquals(expectedTemplate, it)
-            }
-        }
-    }
-
-    @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
-    @Test
-    fun testBuilderWifiNetworkKeys() {
-        // Verify template builder which generates same template with the given different
-        // sequence keys.
-        NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(
-                setOf(TEST_WIFI_KEY1, TEST_WIFI_KEY2)).build().let {
-            val expectedTemplate = NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(
-                    setOf(TEST_WIFI_KEY2, TEST_WIFI_KEY1)).build()
-            assertEquals(expectedTemplate, it)
-        }
-
-        // Verify template which matches non-wifi networks with the given key is invalid.
-        listOf(MATCH_MOBILE, MATCH_CARRIER, MATCH_ETHERNET, MATCH_BLUETOOTH, -1,
-                Integer.MAX_VALUE).forEach { matchRule ->
-            assertFailsWith<IllegalArgumentException> {
-                NetworkTemplate.Builder(matchRule).setWifiNetworkKeys(setOf(TEST_WIFI_KEY1)).build()
-            }
-        }
-
-        // Verify template which matches wifi networks with the given null key is invalid.
-        assertFailsWith<IllegalArgumentException> {
-            NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf(null)).build()
-        }
-
-        // Verify template which matches wifi wildcard with the given empty key set.
-        NetworkTemplate.Builder(MATCH_WIFI).setWifiNetworkKeys(setOf<String>()).build().let {
-            val expectedTemplate = NetworkTemplate(MATCH_WIFI_WILDCARD, null /*subscriberId*/,
-                    arrayOf<String>() /*subscriberIds*/, arrayOf<String>(),
-                    METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, NETWORK_TYPE_ALL,
-                    OEM_MANAGED_ALL, SUBSCRIBER_ID_MATCH_RULE_ALL)
-            assertEquals(expectedTemplate, it)
-        }
-    }
 }
diff --git a/tests/unit/java/android/net/QosSocketFilterTest.java b/tests/unit/java/android/net/QosSocketFilterTest.java
index 91f2cdd..6820b40 100644
--- a/tests/unit/java/android/net/QosSocketFilterTest.java
+++ b/tests/unit/java/android/net/QosSocketFilterTest.java
@@ -16,8 +16,17 @@
 
 package android.net;
 
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_CONNECTED;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import android.os.Build;
 
@@ -29,6 +38,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.net.DatagramSocket;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 
@@ -36,14 +46,14 @@
 @SmallTest
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
 public class QosSocketFilterTest {
-
+    private static final int TEST_NET_ID = 1777;
+    private final Network mNetwork = new Network(TEST_NET_ID);
     @Test
     public void testPortExactMatch() {
         final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4");
         final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.4");
         assertTrue(QosSocketFilter.matchesAddress(
                 new InetSocketAddress(addressA, 10), addressB, 10, 10));
-
     }
 
     @Test
@@ -77,5 +87,90 @@
         assertFalse(QosSocketFilter.matchesAddress(
                 new InetSocketAddress(addressA, 10), addressB, 10, 10));
     }
+
+    @Test
+    public void testAddressMatchWithAnyLocalAddresses() {
+        final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4");
+        final InetAddress addressB = InetAddresses.parseNumericAddress("0.0.0.0");
+        assertTrue(QosSocketFilter.matchesAddress(
+                new InetSocketAddress(addressA, 10), addressB, 10, 10));
+        assertFalse(QosSocketFilter.matchesAddress(
+                new InetSocketAddress(addressB, 10), addressA, 10, 10));
+    }
+
+    @Test
+    public void testProtocolMatch() throws Exception {
+        DatagramSocket socket = new DatagramSocket(new InetSocketAddress("127.0.0.1", 0));
+        socket.connect(new InetSocketAddress("127.0.0.1", socket.getLocalPort() + 10));
+        DatagramSocket socketV6 = new DatagramSocket(new InetSocketAddress("::1", 0));
+        socketV6.connect(new InetSocketAddress("::1", socketV6.getLocalPort() + 10));
+        QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+        QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+        QosSocketInfo socketInfo6 = new QosSocketInfo(mNetwork, socketV6);
+        QosSocketFilter socketFilter6 = new QosSocketFilter(socketInfo6);
+        assertTrue(socketFilter.matchesProtocol(IPPROTO_UDP));
+        assertTrue(socketFilter6.matchesProtocol(IPPROTO_UDP));
+        assertFalse(socketFilter.matchesProtocol(IPPROTO_TCP));
+        assertFalse(socketFilter6.matchesProtocol(IPPROTO_TCP));
+        socket.close();
+        socketV6.close();
+    }
+
+    @Test
+    public void testValidate() throws Exception {
+        DatagramSocket socket = new DatagramSocket(new InetSocketAddress("127.0.0.1", 0));
+        socket.connect(new InetSocketAddress("127.0.0.1", socket.getLocalPort() + 7));
+        DatagramSocket socketV6 = new DatagramSocket(new InetSocketAddress("::1", 0));
+
+        QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+        QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+        QosSocketInfo socketInfo6 = new QosSocketInfo(mNetwork, socketV6);
+        QosSocketFilter socketFilter6 = new QosSocketFilter(socketInfo6);
+        assertEquals(EX_TYPE_FILTER_NONE, socketFilter.validate());
+        assertEquals(EX_TYPE_FILTER_NONE, socketFilter6.validate());
+        socket.close();
+        socketV6.close();
+    }
+
+    @Test
+    public void testValidateUnbind() throws Exception {
+        DatagramSocket socket;
+        socket = new DatagramSocket(null);
+        QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+        QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+        assertEquals(EX_TYPE_FILTER_SOCKET_NOT_BOUND, socketFilter.validate());
+        socket.close();
+    }
+
+    @Test
+    public void testValidateLocalAddressChanged() throws Exception {
+        DatagramSocket socket = new DatagramSocket(null);
+        DatagramSocket socket6 = new DatagramSocket(null);
+        QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+        QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+        QosSocketInfo socketInfo6 = new QosSocketInfo(mNetwork, socket6);
+        QosSocketFilter socketFilter6 = new QosSocketFilter(socketInfo6);
+        socket.bind(new InetSocketAddress("127.0.0.1", 0));
+        socket6.bind(new InetSocketAddress("::1", 0));
+        assertEquals(EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED, socketFilter.validate());
+        assertEquals(EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED, socketFilter6.validate());
+        socket.close();
+        socket6.close();
+    }
+
+    @Test
+    public void testValidateRemoteAddressChanged() throws Exception {
+        DatagramSocket socket;
+        socket = new DatagramSocket(new InetSocketAddress("127.0.0.1", 53137));
+        socket.connect(new InetSocketAddress("127.0.0.1", socket.getLocalPort() + 11));
+        QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+        QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+        assertEquals(EX_TYPE_FILTER_NONE, socketFilter.validate());
+        socket.disconnect();
+        assertEquals(EX_TYPE_FILTER_SOCKET_NOT_CONNECTED, socketFilter.validate());
+        socket.connect(new InetSocketAddress("127.0.0.1", socket.getLocalPort() + 13));
+        assertEquals(EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED, socketFilter.validate());
+        socket.close();
+    }
 }
 
diff --git a/tests/unit/java/android/net/QosSocketInfoTest.java b/tests/unit/java/android/net/QosSocketInfoTest.java
new file mode 100644
index 0000000..749c182
--- /dev/null
+++ b/tests/unit/java/android/net/QosSocketInfoTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_STREAM;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.net.DatagramSocket;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+public class QosSocketInfoTest {
+    @Mock
+    private Network mMockNetwork = mock(Network.class);
+
+    @Test
+    public void testConstructWithSock() throws Exception {
+        ServerSocket server = new ServerSocket();
+        ServerSocket server6 = new ServerSocket();
+
+        InetSocketAddress clientAddr = new InetSocketAddress("127.0.0.1", 0);
+        InetSocketAddress serverAddr = new InetSocketAddress("127.0.0.1", 0);
+        InetSocketAddress clientAddr6 = new InetSocketAddress("::1", 0);
+        InetSocketAddress serverAddr6 = new InetSocketAddress("::1", 0);
+        server.bind(serverAddr);
+        server6.bind(serverAddr6);
+        Socket socket = new Socket(serverAddr.getAddress(), server.getLocalPort(),
+                clientAddr.getAddress(), clientAddr.getPort());
+        Socket socket6 = new Socket(serverAddr6.getAddress(), server6.getLocalPort(),
+                clientAddr6.getAddress(), clientAddr6.getPort());
+        QosSocketInfo sockInfo = new QosSocketInfo(mMockNetwork, socket);
+        QosSocketInfo sockInfo6 = new QosSocketInfo(mMockNetwork, socket6);
+        assertTrue(sockInfo.getLocalSocketAddress()
+                .equals(new InetSocketAddress(socket.getLocalAddress(), socket.getLocalPort())));
+        assertTrue(sockInfo.getRemoteSocketAddress()
+                .equals((InetSocketAddress) socket.getRemoteSocketAddress()));
+        assertEquals(SOCK_STREAM, sockInfo.getSocketType());
+        assertTrue(sockInfo6.getLocalSocketAddress()
+                .equals(new InetSocketAddress(socket6.getLocalAddress(), socket6.getLocalPort())));
+        assertTrue(sockInfo6.getRemoteSocketAddress()
+                .equals((InetSocketAddress) socket6.getRemoteSocketAddress()));
+        assertEquals(SOCK_STREAM, sockInfo6.getSocketType());
+        socket.close();
+        socket6.close();
+        server.close();
+        server6.close();
+    }
+
+    @Test
+    public void testConstructWithDatagramSock() throws Exception {
+        InetSocketAddress clientAddr = new InetSocketAddress("127.0.0.1", 0);
+        InetSocketAddress serverAddr = new InetSocketAddress("127.0.0.1", 0);
+        InetSocketAddress clientAddr6 = new InetSocketAddress("::1", 0);
+        InetSocketAddress serverAddr6 = new InetSocketAddress("::1", 0);
+        DatagramSocket socket = new DatagramSocket(null);
+        socket.setReuseAddress(true);
+        socket.bind(clientAddr);
+        socket.connect(serverAddr);
+        DatagramSocket socket6 = new DatagramSocket(null);
+        socket6.setReuseAddress(true);
+        socket6.bind(clientAddr);
+        socket6.connect(serverAddr);
+        QosSocketInfo sockInfo = new QosSocketInfo(mMockNetwork, socket);
+        QosSocketInfo sockInfo6 = new QosSocketInfo(mMockNetwork, socket6);
+        assertTrue(sockInfo.getLocalSocketAddress()
+                .equals((InetSocketAddress) socket.getLocalSocketAddress()));
+        assertTrue(sockInfo.getRemoteSocketAddress()
+                .equals((InetSocketAddress) socket.getRemoteSocketAddress()));
+        assertEquals(SOCK_DGRAM, sockInfo.getSocketType());
+        assertTrue(sockInfo6.getLocalSocketAddress()
+                .equals((InetSocketAddress) socket6.getLocalSocketAddress()));
+        assertTrue(sockInfo6.getRemoteSocketAddress()
+                .equals((InetSocketAddress) socket6.getRemoteSocketAddress()));
+        assertEquals(SOCK_DGRAM, sockInfo6.getSocketType());
+        socket.close();
+    }
+}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 32ea9c3..3374672 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -104,6 +104,10 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP;
 import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_2;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_3;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_4;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_5;
 import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
 import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
 import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
@@ -136,6 +140,8 @@
 import static com.android.server.ConnectivityService.PREFERENCE_ORDER_PROFILE;
 import static com.android.server.ConnectivityService.PREFERENCE_ORDER_VPN;
 import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType;
+import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackRegister;
+import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister;
 import static com.android.testutils.ConcurrentUtils.await;
 import static com.android.testutils.ConcurrentUtils.durationOf;
 import static com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
@@ -467,6 +473,9 @@
     private static final int TEST_APP_ID_2 = 104;
     private static final int TEST_WORK_PROFILE_APP_UID_2 =
             UserHandle.getUid(TEST_WORK_PROFILE_USER_ID, TEST_APP_ID_2);
+    private static final int TEST_APP_ID_3 = 105;
+    private static final int TEST_APP_ID_4 = 106;
+    private static final int TEST_APP_ID_5 = 107;
 
     private static final String CLAT_PREFIX = "v4-";
     private static final String MOBILE_IFNAME = "test_rmnet_data0";
@@ -796,6 +805,32 @@
         }
     }
 
+    // This was only added in the T SDK, but this test needs to build against the R+S SDKs, too.
+    private static int toSdkSandboxUid(int appUid) {
+        final int firstSdkSandboxUid = 20000;
+        return appUid + (firstSdkSandboxUid - Process.FIRST_APPLICATION_UID);
+    }
+
+    // This function assumes the UID range for user 0 ([1, 99999])
+    private static UidRangeParcel[] uidRangeParcelsExcludingUids(Integer... excludedUids) {
+        int start = 1;
+        Arrays.sort(excludedUids);
+        List<UidRangeParcel> parcels = new ArrayList<UidRangeParcel>();
+        for (int excludedUid : excludedUids) {
+            if (excludedUid == start) {
+                start++;
+            } else {
+                parcels.add(new UidRangeParcel(start, excludedUid - 1));
+                start = excludedUid + 1;
+            }
+        }
+        if (start <= 99999) {
+            parcels.add(new UidRangeParcel(start, 99999));
+        }
+
+        return parcels.toArray(new UidRangeParcel[0]);
+    }
+
     private void waitForIdle() {
         HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
         waitForIdle(mCellNetworkAgent, TIMEOUT_MS);
@@ -5871,7 +5906,7 @@
     }
 
     /**
-     * Validate the callback flow CBS request without carrier privilege.
+     * Validate the service throws if request with CBS but without carrier privilege.
      */
     @Test
     public void testCBSRequestWithoutCarrierPrivilege() throws Exception {
@@ -5880,10 +5915,8 @@
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
 
         mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_DENIED);
-        // Now file the test request and expect it.
-        mCm.requestNetwork(nr, networkCallback);
-        networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null);
-        mCm.unregisterNetworkCallback(networkCallback);
+        // Now file the test request and expect the service throws.
+        assertThrows(SecurityException.class, () -> mCm.requestNetwork(nr, networkCallback));
     }
 
     private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
@@ -9009,10 +9042,16 @@
                 allowList);
         waitForIdle();
 
-        UidRangeParcel firstHalf = new UidRangeParcel(1, VPN_UID - 1);
-        UidRangeParcel secondHalf = new UidRangeParcel(VPN_UID + 1, 99999);
+        final Set<Integer> excludedUids = new ArraySet<Integer>();
+        excludedUids.add(VPN_UID);
+        if (SdkLevel.isAtLeastT()) {
+            // On T onwards, the corresponding SDK sandbox UID should also be excluded
+            excludedUids.add(toSdkSandboxUid(VPN_UID));
+        }
+        final UidRangeParcel[] uidRangeParcels = uidRangeParcelsExcludingUids(
+                excludedUids.toArray(new Integer[0]));
         InOrder inOrder = inOrder(mMockNetd);
-        expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf);
+        expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcels);
 
         // Connect a network when lockdown is active, expect to see it blocked.
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
@@ -9036,7 +9075,7 @@
         vpnUidCallback.assertNoCallback();
         vpnUidDefaultCallback.assertNoCallback();
         vpnDefaultCallbackAsUid.assertNoCallback();
-        expectNetworkRejectNonSecureVpn(inOrder, false, firstHalf, secondHalf);
+        expectNetworkRejectNonSecureVpn(inOrder, false, uidRangeParcels);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -9053,13 +9092,14 @@
         vpnUidDefaultCallback.assertNoCallback();
         vpnDefaultCallbackAsUid.assertNoCallback();
 
-        // The following requires that the UID of this test package is greater than VPN_UID. This
-        // is always true in practice because a plain AOSP build with no apps installed has almost
-        // 200 packages installed.
-        final UidRangeParcel piece1 = new UidRangeParcel(1, VPN_UID - 1);
-        final UidRangeParcel piece2 = new UidRangeParcel(VPN_UID + 1, uid - 1);
-        final UidRangeParcel piece3 = new UidRangeParcel(uid + 1, 99999);
-        expectNetworkRejectNonSecureVpn(inOrder, true, piece1, piece2, piece3);
+        excludedUids.add(uid);
+        if (SdkLevel.isAtLeastT()) {
+            // On T onwards, the corresponding SDK sandbox UID should also be excluded
+            excludedUids.add(toSdkSandboxUid(uid));
+        }
+        final UidRangeParcel[] uidRangeParcelsAlsoExcludingUs = uidRangeParcelsExcludingUids(
+                excludedUids.toArray(new Integer[0]));
+        expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcelsAlsoExcludingUs);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -9085,12 +9125,12 @@
         // Everything should now be blocked.
         mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
         waitForIdle();
-        expectNetworkRejectNonSecureVpn(inOrder, false, piece1, piece2, piece3);
+        expectNetworkRejectNonSecureVpn(inOrder, false, uidRangeParcelsAlsoExcludingUs);
         allowList.clear();
         mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
                 allowList);
         waitForIdle();
-        expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf);
+        expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcels);
         defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
         assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
         vpnUidCallback.assertNoCallback();
@@ -11937,16 +11977,14 @@
         mQosCallbackMockHelper.registerQosCallback(
                 mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
 
-        final NetworkAgentWrapper.CallbackType.OnQosCallbackRegister cbRegister1 =
-                (NetworkAgentWrapper.CallbackType.OnQosCallbackRegister)
-                        wrapper.getCallbackHistory().poll(1000, x -> true);
+        final OnQosCallbackRegister cbRegister1 =
+                (OnQosCallbackRegister) wrapper.getCallbackHistory().poll(1000, x -> true);
         assertNotNull(cbRegister1);
 
         final int registerCallbackId = cbRegister1.mQosCallbackId;
         mService.unregisterQosCallback(mQosCallbackMockHelper.mCallback);
-        final NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister cbUnregister;
-        cbUnregister = (NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister)
-                wrapper.getCallbackHistory().poll(1000, x -> true);
+        final OnQosCallbackUnregister cbUnregister =
+                (OnQosCallbackUnregister) wrapper.getCallbackHistory().poll(1000, x -> true);
         assertNotNull(cbUnregister);
         assertEquals(registerCallbackId, cbUnregister.mQosCallbackId);
         assertNull(wrapper.getCallbackHistory().poll(200, x -> true));
@@ -12025,6 +12063,86 @@
                         && session.getSessionType() == QosSession.TYPE_NR_BEARER));
     }
 
+    @Test @IgnoreUpTo(SC_V2)
+    public void testQosCallbackAvailableOnValidationError() throws Exception {
+        mQosCallbackMockHelper = new QosCallbackMockHelper();
+        final NetworkAgentWrapper wrapper = mQosCallbackMockHelper.mAgentWrapper;
+        final int sessionId = 10;
+        final int qosCallbackId = 1;
+
+        doReturn(QosCallbackException.EX_TYPE_FILTER_NONE)
+                .when(mQosCallbackMockHelper.mFilter).validate();
+        mQosCallbackMockHelper.registerQosCallback(
+                mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
+        OnQosCallbackRegister cbRegister1 =
+                (OnQosCallbackRegister) wrapper.getCallbackHistory().poll(1000, x -> true);
+        assertNotNull(cbRegister1);
+        final int registerCallbackId = cbRegister1.mQosCallbackId;
+
+        waitForIdle();
+
+        doReturn(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED)
+                .when(mQosCallbackMockHelper.mFilter).validate();
+        final EpsBearerQosSessionAttributes attributes = new EpsBearerQosSessionAttributes(
+                1, 2, 3, 4, 5, new ArrayList<>());
+        mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
+                .sendQosSessionAvailable(qosCallbackId, sessionId, attributes);
+        waitForIdle();
+
+        final NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister cbUnregister;
+        cbUnregister = (NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister)
+                wrapper.getCallbackHistory().poll(1000, x -> true);
+        assertNotNull(cbUnregister);
+        assertEquals(registerCallbackId, cbUnregister.mQosCallbackId);
+        waitForIdle();
+        verify(mQosCallbackMockHelper.mCallback)
+                .onError(eq(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED));
+    }
+
+    @Test @IgnoreUpTo(SC_V2)
+    public void testQosCallbackLostOnValidationError() throws Exception {
+        mQosCallbackMockHelper = new QosCallbackMockHelper();
+        final int sessionId = 10;
+        final int qosCallbackId = 1;
+
+        doReturn(QosCallbackException.EX_TYPE_FILTER_NONE)
+                .when(mQosCallbackMockHelper.mFilter).validate();
+        mQosCallbackMockHelper.registerQosCallback(
+                mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
+        waitForIdle();
+        EpsBearerQosSessionAttributes attributes =
+                sendQosSessionEvent(qosCallbackId, sessionId, true);
+        waitForIdle();
+
+        verify(mQosCallbackMockHelper.mCallback).onQosEpsBearerSessionAvailable(argThat(session ->
+                session.getSessionId() == sessionId
+                        && session.getSessionType() == QosSession.TYPE_EPS_BEARER), eq(attributes));
+
+        doReturn(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED)
+                .when(mQosCallbackMockHelper.mFilter).validate();
+
+        sendQosSessionEvent(qosCallbackId, sessionId, false);
+        waitForIdle();
+        verify(mQosCallbackMockHelper.mCallback)
+                .onError(eq(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED));
+    }
+
+    private EpsBearerQosSessionAttributes sendQosSessionEvent(
+            int qosCallbackId, int sessionId, boolean available) {
+        if (available) {
+            final EpsBearerQosSessionAttributes attributes = new EpsBearerQosSessionAttributes(
+                    1, 2, 3, 4, 5, new ArrayList<>());
+            mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
+                    .sendQosSessionAvailable(qosCallbackId, sessionId, attributes);
+            return attributes;
+        } else {
+            mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
+                    .sendQosSessionLost(qosCallbackId, sessionId, QosSession.TYPE_EPS_BEARER);
+            return null;
+        }
+
+    }
+
     @Test
     public void testQosCallbackTooManyRequests() throws Exception {
         mQosCallbackMockHelper = new QosCallbackMockHelper();
@@ -14548,7 +14666,7 @@
         profileNetworkPreferenceBuilder.setPreference(
                 PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
         profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(
-                NetworkCapabilities.NET_ENTERPRISE_ID_2);
+                NET_ENTERPRISE_ID_2);
         registerDefaultNetworkCallbacks();
         testPreferenceForUserNetworkUpDownForGivenPreference(
                 profileNetworkPreferenceBuilder.build(), true,
@@ -14573,6 +14691,393 @@
     }
 
     /**
+     * Make sure per-profile networking preference throws exception when default preference
+     * is set along with enterprise preference.
+     */
+    @Test
+    public void testPreferenceWithInvalidPreferenceDefaultAndEnterpriseTogether()
+            throws Exception {
+        final UserHandle testHandle = setupEnterpriseNetwork();
+        mServiceContext.setWorkProfile(testHandle, true);
+
+        final int testWorkProfileAppUid1 =
+                UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID);
+        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder1 =
+                new ProfileNetworkPreference.Builder();
+        profileNetworkPreferenceBuilder1.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+        profileNetworkPreferenceBuilder1.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+        profileNetworkPreferenceBuilder1.setIncludedUids(new int[]{testWorkProfileAppUid1});
+
+        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder2 =
+                new ProfileNetworkPreference.Builder();
+        profileNetworkPreferenceBuilder2.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT);
+        final TestOnCompleteListener listener = new TestOnCompleteListener();
+        Assert.assertThrows(IllegalArgumentException.class,
+                () -> mCm.setProfileNetworkPreferences(
+                        testHandle, List.of(profileNetworkPreferenceBuilder1.build(),
+                                profileNetworkPreferenceBuilder2.build()),
+                        r -> r.run(), listener));
+        Assert.assertThrows(IllegalArgumentException.class,
+                () -> mCm.setProfileNetworkPreferences(
+                        testHandle, List.of(profileNetworkPreferenceBuilder2.build(),
+                                profileNetworkPreferenceBuilder1.build()),
+                        r -> r.run(), listener));
+    }
+
+    /**
+     * Make sure per profile network preferences behave as expected when two slices with
+     * two different apps within same user profile is configured
+     * Make sure per profile network preferences overrides with latest preference when
+     * same user preference is set twice
+     */
+    @Test
+    public void testSetPreferenceWithOverridingPreference()
+            throws Exception {
+        final InOrder inOrder = inOrder(mMockNetd);
+        final UserHandle testHandle = setupEnterpriseNetwork();
+        mServiceContext.setWorkProfile(testHandle, true);
+        registerDefaultNetworkCallbacks();
+
+        final TestNetworkCallback appCb1 = new TestNetworkCallback();
+        final TestNetworkCallback appCb2 = new TestNetworkCallback();
+        final TestNetworkCallback appCb3 = new TestNetworkCallback();
+
+        final int testWorkProfileAppUid1 =
+                UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID);
+        final int testWorkProfileAppUid2 =
+                UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_2);
+        final int testWorkProfileAppUid3 =
+                UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_3);
+
+        registerDefaultNetworkCallbackAsUid(appCb1, testWorkProfileAppUid1);
+        registerDefaultNetworkCallbackAsUid(appCb2, testWorkProfileAppUid2);
+        registerDefaultNetworkCallbackAsUid(appCb3, testWorkProfileAppUid3);
+
+        // Connect both a regular cell agent and an enterprise network first.
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true);
+
+        final TestNetworkAgentWrapper workAgent1 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_1);
+        final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_2);
+        workAgent1.connect(true);
+        workAgent2.connect(true);
+
+        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+
+        appCb1.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        appCb2.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        appCb3.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+
+        verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+                mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
+        verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+                workAgent1.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+        verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+                workAgent2.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+
+        final TestOnCompleteListener listener = new TestOnCompleteListener();
+
+        // Set preferences for testHandle to map testWorkProfileAppUid1 to
+        // NET_ENTERPRISE_ID_1 and testWorkProfileAppUid2 to NET_ENTERPRISE_ID_2.
+        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder1 =
+                new ProfileNetworkPreference.Builder();
+        profileNetworkPreferenceBuilder1.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+        profileNetworkPreferenceBuilder1.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+        profileNetworkPreferenceBuilder1.setIncludedUids(new int[]{testWorkProfileAppUid1});
+
+        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder2 =
+                new ProfileNetworkPreference.Builder();
+        profileNetworkPreferenceBuilder2.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+        profileNetworkPreferenceBuilder2.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_2);
+        profileNetworkPreferenceBuilder2.setIncludedUids(new int[]{testWorkProfileAppUid2});
+
+        mCm.setProfileNetworkPreferences(testHandle,
+                List.of(profileNetworkPreferenceBuilder1.build(),
+                        profileNetworkPreferenceBuilder2.build()),
+                r -> r.run(), listener);
+        listener.expectOnComplete();
+        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+                workAgent2.getNetwork().netId,
+                uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
+                PREFERENCE_ORDER_PROFILE));
+        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+                workAgent1.getNetwork().netId,
+                uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
+                PREFERENCE_ORDER_PROFILE));
+
+        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+        appCb1.expectAvailableCallbacksValidated(workAgent1);
+        appCb2.expectAvailableCallbacksValidated(workAgent2);
+
+        // Set preferences for testHandle to map testWorkProfileAppUid3 to
+        // to NET_ENTERPRISE_ID_1.
+        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder3 =
+                new ProfileNetworkPreference.Builder();
+        profileNetworkPreferenceBuilder3.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+        profileNetworkPreferenceBuilder3.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+        profileNetworkPreferenceBuilder3.setIncludedUids(new int[]{testWorkProfileAppUid3});
+
+        mCm.setProfileNetworkPreferences(testHandle,
+                List.of(profileNetworkPreferenceBuilder3.build()),
+                r -> r.run(), listener);
+        listener.expectOnComplete();
+        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+                workAgent1.getNetwork().netId,
+                uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
+                PREFERENCE_ORDER_PROFILE));
+        verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
+                workAgent2.getNetwork().netId,
+                uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
+                PREFERENCE_ORDER_PROFILE));
+        verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
+                workAgent1.getNetwork().netId,
+                uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
+                PREFERENCE_ORDER_PROFILE));
+
+        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+        appCb3.expectAvailableCallbacksValidated(workAgent1);
+        appCb2.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        appCb1.expectAvailableCallbacksValidated(mCellNetworkAgent);
+
+        // Set the preferences for testHandle to default.
+        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+                new ProfileNetworkPreference.Builder();
+        profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT);
+
+        mCm.setProfileNetworkPreferences(testHandle,
+                List.of(profileNetworkPreferenceBuilder.build()),
+                r -> r.run(), listener);
+        listener.expectOnComplete();
+        verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
+                workAgent1.getNetwork().netId,
+                uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
+                PREFERENCE_ORDER_PROFILE));
+
+        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, appCb1, appCb2);
+        appCb3.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        workAgent2.disconnect();
+        mCellNetworkAgent.disconnect();
+
+        mCm.unregisterNetworkCallback(appCb1);
+        mCm.unregisterNetworkCallback(appCb2);
+        mCm.unregisterNetworkCallback(appCb3);
+        // Other callbacks will be unregistered by tearDown()
+    }
+
+    /**
+     * Make sure per profile network preferences behave as expected when multiple slices with
+     * multiple different apps within same user profile is configured.
+     */
+    @Test
+    public void testSetPreferenceWithMultiplePreferences()
+            throws Exception {
+        final InOrder inOrder = inOrder(mMockNetd);
+
+        final UserHandle testHandle = setupEnterpriseNetwork();
+        mServiceContext.setWorkProfile(testHandle, true);
+        registerDefaultNetworkCallbacks();
+
+        final TestNetworkCallback appCb1 = new TestNetworkCallback();
+        final TestNetworkCallback appCb2 = new TestNetworkCallback();
+        final TestNetworkCallback appCb3 = new TestNetworkCallback();
+        final TestNetworkCallback appCb4 = new TestNetworkCallback();
+        final TestNetworkCallback appCb5 = new TestNetworkCallback();
+
+        final int testWorkProfileAppUid1 =
+                UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID);
+        final int testWorkProfileAppUid2 =
+                UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_2);
+        final int testWorkProfileAppUid3 =
+                UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_3);
+        final int testWorkProfileAppUid4 =
+                UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_4);
+        final int testWorkProfileAppUid5 =
+                UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_5);
+
+        registerDefaultNetworkCallbackAsUid(appCb1, testWorkProfileAppUid1);
+        registerDefaultNetworkCallbackAsUid(appCb2, testWorkProfileAppUid2);
+        registerDefaultNetworkCallbackAsUid(appCb3, testWorkProfileAppUid3);
+        registerDefaultNetworkCallbackAsUid(appCb4, testWorkProfileAppUid4);
+        registerDefaultNetworkCallbackAsUid(appCb5, testWorkProfileAppUid5);
+
+        // Connect both a regular cell agent and an enterprise network first.
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true);
+
+        final TestNetworkAgentWrapper workAgent1 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_1);
+        final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_2);
+        final TestNetworkAgentWrapper workAgent3 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_3);
+        final TestNetworkAgentWrapper workAgent4 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_4);
+        final TestNetworkAgentWrapper workAgent5 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_5);
+
+        workAgent1.connect(true);
+        workAgent2.connect(true);
+        workAgent3.connect(true);
+        workAgent4.connect(true);
+        workAgent5.connect(true);
+
+        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        appCb1.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        appCb2.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        appCb3.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        appCb4.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        appCb5.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+
+        verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+                mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
+        verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+                workAgent1.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+        verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+                workAgent2.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+        verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+                workAgent3.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+        verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+                workAgent4.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+        verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+                workAgent5.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+
+        final TestOnCompleteListener listener = new TestOnCompleteListener();
+
+        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder1 =
+                new ProfileNetworkPreference.Builder();
+        profileNetworkPreferenceBuilder1.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+        profileNetworkPreferenceBuilder1.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+        profileNetworkPreferenceBuilder1.setIncludedUids(new int[]{testWorkProfileAppUid1});
+
+        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder2 =
+                new ProfileNetworkPreference.Builder();
+        profileNetworkPreferenceBuilder2.setPreference(
+                PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+        profileNetworkPreferenceBuilder2.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_2);
+        profileNetworkPreferenceBuilder2.setIncludedUids(new int[]{testWorkProfileAppUid2});
+
+        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder3 =
+                new ProfileNetworkPreference.Builder();
+        profileNetworkPreferenceBuilder3.setPreference(
+                PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+        profileNetworkPreferenceBuilder3.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_3);
+        profileNetworkPreferenceBuilder3.setIncludedUids(new int[]{testWorkProfileAppUid3});
+
+        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder4 =
+                new ProfileNetworkPreference.Builder();
+        profileNetworkPreferenceBuilder4.setPreference(
+                PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
+        profileNetworkPreferenceBuilder4.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_4);
+        profileNetworkPreferenceBuilder4.setIncludedUids(new int[]{testWorkProfileAppUid4});
+
+        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder5 =
+                new ProfileNetworkPreference.Builder();
+        profileNetworkPreferenceBuilder5.setPreference(
+                PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+        profileNetworkPreferenceBuilder5.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_5);
+        profileNetworkPreferenceBuilder5.setIncludedUids(new int[]{testWorkProfileAppUid5});
+
+        mCm.setProfileNetworkPreferences(testHandle,
+                List.of(profileNetworkPreferenceBuilder1.build(),
+                        profileNetworkPreferenceBuilder2.build(),
+                        profileNetworkPreferenceBuilder3.build(),
+                        profileNetworkPreferenceBuilder4.build(),
+                        profileNetworkPreferenceBuilder5.build()),
+                r -> r.run(), listener);
+
+        listener.expectOnComplete();
+
+        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+                workAgent1.getNetwork().netId,
+                uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
+                PREFERENCE_ORDER_PROFILE));
+        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+                workAgent2.getNetwork().netId,
+                uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
+                PREFERENCE_ORDER_PROFILE));
+        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+                workAgent3.getNetwork().netId,
+                uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
+                PREFERENCE_ORDER_PROFILE));
+        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+                workAgent4.getNetwork().netId,
+                uidRangeFor(testHandle, profileNetworkPreferenceBuilder4.build()),
+                PREFERENCE_ORDER_PROFILE));
+        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+                workAgent5.getNetwork().netId,
+                uidRangeFor(testHandle, profileNetworkPreferenceBuilder5.build()),
+                PREFERENCE_ORDER_PROFILE));
+
+        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+        appCb1.expectAvailableCallbacksValidated(workAgent1);
+        appCb2.expectAvailableCallbacksValidated(workAgent2);
+        appCb3.expectAvailableCallbacksValidated(workAgent3);
+        appCb4.expectAvailableCallbacksValidated(workAgent4);
+        appCb5.expectAvailableCallbacksValidated(workAgent5);
+
+        workAgent1.disconnect();
+        workAgent2.disconnect();
+        workAgent3.disconnect();
+        workAgent4.disconnect();
+        workAgent5.disconnect();
+
+        appCb1.expectCallback(CallbackEntry.LOST, workAgent1);
+        appCb2.expectCallback(CallbackEntry.LOST, workAgent2);
+        appCb3.expectCallback(CallbackEntry.LOST, workAgent3);
+        appCb4.expectCallback(CallbackEntry.LOST, workAgent4);
+        appCb5.expectCallback(CallbackEntry.LOST, workAgent5);
+
+        appCb1.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        appCb2.assertNoCallback();
+        appCb3.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        appCb4.assertNoCallback();
+        appCb5.expectAvailableCallbacksValidated(mCellNetworkAgent);
+
+        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+                mCellNetworkAgent.getNetwork().netId,
+                uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
+                PREFERENCE_ORDER_PROFILE));
+        verify(mMockNetd, never()).networkAddUidRangesParcel(new NativeUidRangeConfig(
+                mCellNetworkAgent.getNetwork().netId,
+                uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
+                PREFERENCE_ORDER_PROFILE));
+        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+                mCellNetworkAgent.getNetwork().netId,
+                uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
+                PREFERENCE_ORDER_PROFILE));
+        verify(mMockNetd, never()).networkAddUidRangesParcel(new NativeUidRangeConfig(
+                mCellNetworkAgent.getNetwork().netId,
+                uidRangeFor(testHandle, profileNetworkPreferenceBuilder4.build()),
+                PREFERENCE_ORDER_PROFILE));
+        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+                mCellNetworkAgent.getNetwork().netId,
+                uidRangeFor(testHandle, profileNetworkPreferenceBuilder5.build()),
+                PREFERENCE_ORDER_PROFILE));
+
+        mSystemDefaultNetworkCallback.assertNoCallback();
+        mDefaultNetworkCallback.assertNoCallback();
+
+        // Set the preferences for testHandle to default.
+        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+                new ProfileNetworkPreference.Builder();
+        profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT);
+
+        mCm.setProfileNetworkPreferences(testHandle,
+                List.of(profileNetworkPreferenceBuilder.build()),
+                r -> r.run(), listener);
+        listener.expectOnComplete();
+        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, appCb1, appCb3,
+                appCb5);
+        appCb2.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        appCb4.expectAvailableCallbacksValidated(mCellNetworkAgent);
+        mCellNetworkAgent.disconnect();
+
+        mCm.unregisterNetworkCallback(appCb1);
+        mCm.unregisterNetworkCallback(appCb2);
+        mCm.unregisterNetworkCallback(appCb3);
+        mCm.unregisterNetworkCallback(appCb4);
+        mCm.unregisterNetworkCallback(appCb5);
+        // Other callbacks will be unregistered by tearDown()
+    }
+
+    /**
      * Test that, in a given networking context, calling setPreferenceForUser to set per-profile
      * defaults on then off works as expected.
      */
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 6da4f07..11fbcb9 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -28,9 +28,11 @@
 import static android.net.INetd.IF_STATE_DOWN;
 import static android.net.INetd.IF_STATE_UP;
 import static android.net.VpnManager.TYPE_VPN_PLATFORM;
+import static android.os.Build.VERSION_CODES.S_V2;
 import static android.os.UserHandle.PER_USER_RANGE;
 
 import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
+import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import static com.android.testutils.MiscAsserts.assertThrows;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -86,8 +88,11 @@
 import android.net.LinkProperties;
 import android.net.LocalSocket;
 import android.net.Network;
+import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkProvider;
 import android.net.RouteInfo;
 import android.net.UidRangeParcel;
 import android.net.VpnManager;
@@ -104,6 +109,7 @@
 import android.os.Bundle;
 import android.os.ConditionVariable;
 import android.os.INetworkManagementService;
+import android.os.Looper;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.UserHandle;
@@ -121,13 +127,16 @@
 import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnProfile;
+import com.android.internal.util.HexDump;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.server.IpSecService;
+import com.android.server.vcn.util.PersistableBundleUtils;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.AdditionalAnswers;
@@ -164,10 +173,13 @@
  */
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(VERSION_CODES.R)
+@IgnoreUpTo(VERSION_CODES.S_V2)
 public class VpnTest {
     private static final String TAG = "VpnTest";
 
+    @Rule
+    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
     // Mock users
     static final UserInfo primaryUser = new UserInfo(27, "Primary", FLAG_ADMIN | FLAG_PRIMARY);
     static final UserInfo secondaryUser = new UserInfo(15, "Secondary", FLAG_ADMIN);
@@ -191,14 +203,16 @@
     private static final String TEST_IFACE_NAME = "TEST_IFACE";
     private static final int TEST_TUNNEL_RESOURCE_ID = 0x2345;
     private static final long TEST_TIMEOUT_MS = 500L;
-
+    private static final String PRIMARY_USER_APP_EXCLUDE_KEY =
+            "VPN_APP_EXCLUDED_27_com.testvpn.vpn";
     /**
      * Names and UIDs for some fake packages. Important points:
      *  - UID is ordered increasing.
      *  - One pair of packages have consecutive UIDs.
      */
     static final String[] PKGS = {"com.example", "org.example", "net.example", "web.vpn"};
-    static final int[] PKG_UIDS = {66, 77, 78, 400};
+    static final String PKGS_BYTES = getPackageByteString(List.of(PKGS));
+    static final int[] PKG_UIDS = {10066, 10077, 10078, 10400};
 
     // Mock packages
     static final Map<String, Integer> mPackages = new ArrayMap<>();
@@ -311,6 +325,17 @@
         return new Range<Integer>(start, stop);
     }
 
+    private static String getPackageByteString(List<String> packages) {
+        try {
+            return HexDump.toHexString(
+                    PersistableBundleUtils.toDiskStableBytes(PersistableBundleUtils.fromList(
+                            packages, PersistableBundleUtils.STRING_SERIALIZER)),
+                        true /* upperCase */);
+        } catch (IOException e) {
+            return null;
+        }
+    }
+
     @Test
     public void testRestrictedProfilesAreAddedToVpn() {
         setMockedUsers(primaryUser, secondaryUser, restrictedProfileA, restrictedProfileB);
@@ -360,7 +385,11 @@
                 Arrays.asList(packages), null /* disallowedApplications */);
         assertEquals(rangeSet(
                 uidRange(userStart + PKG_UIDS[0], userStart + PKG_UIDS[0]),
-                uidRange(userStart + PKG_UIDS[1], userStart + PKG_UIDS[2])),
+                uidRange(userStart + PKG_UIDS[1], userStart + PKG_UIDS[2]),
+                uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[0]),
+                         Process.toSdkSandboxUid(userStart + PKG_UIDS[0])),
+                uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[1]),
+                         Process.toSdkSandboxUid(userStart + PKG_UIDS[2]))),
                 allow);
 
         // Denied list
@@ -371,7 +400,11 @@
                 uidRange(userStart, userStart + PKG_UIDS[0] - 1),
                 uidRange(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
                 /* Empty range between UIDS[1] and UIDS[2], should be excluded, */
-                uidRange(userStart + PKG_UIDS[2] + 1, userStop)),
+                uidRange(userStart + PKG_UIDS[2] + 1,
+                         Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+                         Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)),
                 disallow);
     }
 
@@ -412,18 +445,24 @@
         assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
         verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
         }));
 
         // Switch to another app.
         assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
         verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
         }));
         verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart, userStart + PKG_UIDS[3] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[3] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
         }));
     }
 
@@ -438,17 +477,25 @@
                 PKGS[1], true, Collections.singletonList(PKGS[2])));
         verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[]  {
                 new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[2] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1]) - 1),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
         }));
         // Change allowed app list to PKGS[3].
         assertTrue(vpn.setAlwaysOnPackage(
                 PKGS[1], true, Collections.singletonList(PKGS[3])));
         verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart + PKG_UIDS[2] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
         }));
         verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[3] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
         }));
 
         // Change the VPN app.
@@ -456,32 +503,52 @@
                 PKGS[0], true, Collections.singletonList(PKGS[3])));
         verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1)
+                new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1),
+                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1))
         }));
         verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart, userStart + PKG_UIDS[0] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1)
+                new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1),
+                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1))
         }));
 
         // Remove the list of allowed packages.
         assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
         verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[3] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
         }));
         verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStop),
+                new UidRangeParcel(userStart + PKG_UIDS[0] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), userStop),
         }));
 
         // Add the list of allowed packages.
         assertTrue(vpn.setAlwaysOnPackage(
                 PKGS[0], true, Collections.singletonList(PKGS[1])));
         verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[0] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), userStop),
         }));
         verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
         }));
 
         // Try allowing a package with a comma, should be rejected.
@@ -494,11 +561,19 @@
                 PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app")));
         verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
         }));
         verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[2] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[2] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[2] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
         }));
     }
 
@@ -543,7 +618,10 @@
         };
         final UidRangeParcel[] exceptPkg0 = {
             new UidRangeParcel(entireUser[0].start, entireUser[0].start + PKG_UIDS[0] - 1),
-            new UidRangeParcel(entireUser[0].start + PKG_UIDS[0] + 1, entireUser[0].stop)
+            new UidRangeParcel(entireUser[0].start + PKG_UIDS[0] + 1,
+                               Process.toSdkSandboxUid(entireUser[0].start + PKG_UIDS[0] - 1)),
+            new UidRangeParcel(Process.toSdkSandboxUid(entireUser[0].start + PKG_UIDS[0] + 1),
+                               entireUser[0].stop),
         };
 
         final InOrder order = inOrder(mConnectivityManager);
@@ -711,6 +789,47 @@
         }
     }
 
+    private Vpn prepareVpnForVerifyAppExclusionList() throws Exception {
+        final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+        when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+                .thenReturn(mVpnProfile.encode());
+        when(mVpnProfileStore.get(PRIMARY_USER_APP_EXCLUDE_KEY))
+                .thenReturn(HexDump.hexStringToByteArray(PKGS_BYTES));
+
+        vpn.startVpnProfile(TEST_VPN_PKG);
+        verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
+        vpn.mNetworkAgent = new NetworkAgent(mContext, Looper.getMainLooper(), TAG,
+                new NetworkCapabilities.Builder().build(), new LinkProperties(), 10 /* score */,
+                new NetworkAgentConfig.Builder().build(),
+                new NetworkProvider(mContext, Looper.getMainLooper(), TAG)) {};
+        return vpn;
+    }
+
+    @Test @IgnoreUpTo(S_V2)
+    public void testSetAndGetAppExclusionList() throws Exception {
+        final Vpn vpn = prepareVpnForVerifyAppExclusionList();
+        verify(mVpnProfileStore, never()).put(eq(PRIMARY_USER_APP_EXCLUDE_KEY), any());
+        vpn.setAppExclusionList(TEST_VPN_PKG, Arrays.asList(PKGS));
+        verify(mVpnProfileStore)
+                .put(eq(PRIMARY_USER_APP_EXCLUDE_KEY),
+                     eq(HexDump.hexStringToByteArray(PKGS_BYTES)));
+        assertEquals(vpn.createUserAndRestrictedProfilesRanges(
+                primaryUser.id, null, Arrays.asList(PKGS)),
+                vpn.mNetworkCapabilities.getUids());
+        assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
+    }
+
+    @Test @IgnoreUpTo(S_V2)
+    public void testSetAndGetAppExclusionListRestrictedUser() throws Exception {
+        final Vpn vpn = prepareVpnForVerifyAppExclusionList();
+        // Mock it to restricted profile
+        when(mUserManager.getUserInfo(anyInt())).thenReturn(restrictedProfileA);
+        // Restricted users cannot configure VPNs
+        assertThrows(SecurityException.class,
+                () -> vpn.setAppExclusionList(TEST_VPN_PKG, new ArrayList<>()));
+        assertThrows(SecurityException.class, () -> vpn.getAppExclusionList(TEST_VPN_PKG));
+    }
+
     @Test
     public void testProvisionVpnProfilePreconsented() throws Exception {
         final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
index 5f9d1ff..e8c9637 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -32,6 +32,7 @@
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
@@ -64,6 +65,7 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
 import java.util.Objects;
 
 /**
@@ -86,11 +88,19 @@
     private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
     private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
 
+    private static final int PID_SYSTEM = 1234;
+    private static final int PID_RED = 1235;
+    private static final int PID_BLUE = 1236;
+
     private static final int UID_RED = UserHandle.PER_USER_RANGE + 1;
     private static final int UID_BLUE = UserHandle.PER_USER_RANGE + 2;
     private static final int UID_GREEN = UserHandle.PER_USER_RANGE + 3;
     private static final int UID_ANOTHER_USER = 2 * UserHandle.PER_USER_RANGE + 4;
 
+    private static final String PACKAGE_SYSTEM = "android";
+    private static final String PACKAGE_RED = "RED";
+    private static final String PACKAGE_BLUE = "BLUE";
+
     private static final long WAIT_TIMEOUT_MS = 500;
     private static final long THRESHOLD_BYTES = 2 * MB_IN_BYTES;
     private static final long BASE_BYTES = 7 * MB_IN_BYTES;
@@ -131,14 +141,15 @@
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdTooLowBytes);
 
         final DataUsageRequest requestByApp = mStatsObservers.register(mContext, inputRequest,
-                mUsageCallback, UID_RED, NetworkStatsAccess.Level.DEVICE);
+                mUsageCallback, PID_RED , UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.DEVICE);
         assertTrue(requestByApp.requestId > 0);
         assertTrue(Objects.equals(sTemplateWifi, requestByApp.template));
         assertEquals(thresholdTooLowBytes, requestByApp.thresholdInBytes);
 
         // Verify the threshold requested by system uid won't be overridden.
         final DataUsageRequest requestBySystem = mStatsObservers.register(mContext, inputRequest,
-                mUsageCallback, Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+                mUsageCallback, PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM,
+                NetworkStatsAccess.Level.DEVICE);
         assertTrue(requestBySystem.requestId > 0);
         assertTrue(Objects.equals(sTemplateWifi, requestBySystem.template));
         assertEquals(1, requestBySystem.thresholdInBytes);
@@ -151,7 +162,7 @@
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, highThresholdBytes);
 
         DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
-                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+                PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
         assertTrue(request.requestId > 0);
         assertTrue(Objects.equals(sTemplateWifi, request.template));
         assertEquals(highThresholdBytes, request.thresholdInBytes);
@@ -163,19 +174,64 @@
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, THRESHOLD_BYTES);
 
         DataUsageRequest request1 = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
-                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+                PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
         assertTrue(request1.requestId > 0);
         assertTrue(Objects.equals(sTemplateWifi, request1.template));
         assertEquals(THRESHOLD_BYTES, request1.thresholdInBytes);
 
         DataUsageRequest request2 = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
-                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+                PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
         assertTrue(request2.requestId > request1.requestId);
         assertTrue(Objects.equals(sTemplateWifi, request2.template));
         assertEquals(THRESHOLD_BYTES, request2.thresholdInBytes);
     }
 
     @Test
+    public void testRegister_limit() throws Exception {
+        final DataUsageRequest inputRequest = new DataUsageRequest(
+                DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, THRESHOLD_BYTES);
+
+        // Register maximum requests for red.
+        final ArrayList<DataUsageRequest> redRequests = new ArrayList<>();
+        for (int i = 0; i < NetworkStatsObservers.MAX_REQUESTS_PER_UID; i++) {
+            final DataUsageRequest returnedRequest =
+                    mStatsObservers.register(mContext, inputRequest, mUsageCallback,
+                            PID_RED, UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.DEVICE);
+            redRequests.add(returnedRequest);
+            assertTrue(returnedRequest.requestId > 0);
+        }
+
+        // Verify request exceeds the limit throws.
+        assertThrows(IllegalStateException.class, () ->
+                mStatsObservers.register(mContext, inputRequest, mUsageCallback,
+                    PID_RED, UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.DEVICE));
+
+        // Verify another uid is not affected.
+        final ArrayList<DataUsageRequest> blueRequests = new ArrayList<>();
+        for (int i = 0; i < NetworkStatsObservers.MAX_REQUESTS_PER_UID; i++) {
+            final DataUsageRequest returnedRequest =
+                    mStatsObservers.register(mContext, inputRequest, mUsageCallback,
+                            PID_BLUE, UID_BLUE, PACKAGE_BLUE, NetworkStatsAccess.Level.DEVICE);
+            blueRequests.add(returnedRequest);
+            assertTrue(returnedRequest.requestId > 0);
+        }
+
+        // Again, verify request exceeds the limit throws for the 2nd uid.
+        assertThrows(IllegalStateException.class, () ->
+                mStatsObservers.register(mContext, inputRequest, mUsageCallback,
+                        PID_RED, UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.DEVICE));
+
+        // Unregister all registered requests. Note that exceptions cannot be tested since
+        // unregister is handled in the handler thread.
+        for (final DataUsageRequest request : redRequests) {
+            mStatsObservers.unregister(request, UID_RED);
+        }
+        for (final DataUsageRequest request : blueRequests) {
+            mStatsObservers.unregister(request, UID_BLUE);
+        }
+    }
+
+    @Test
     public void testUnregister_unknownRequest_noop() throws Exception {
         DataUsageRequest unknownRequest = new DataUsageRequest(
                 123456 /* id */, sTemplateWifi, THRESHOLD_BYTES);
@@ -189,7 +245,7 @@
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
 
         DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
-                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+                PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
         assertTrue(request.requestId > 0);
         assertTrue(Objects.equals(sTemplateImsi1, request.template));
         assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -209,7 +265,7 @@
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
 
         DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
-                UID_RED, NetworkStatsAccess.Level.DEVICE);
+                PID_RED, UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.DEVICE);
         assertTrue(request.requestId > 0);
         assertTrue(Objects.equals(sTemplateImsi1, request.template));
         assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -237,7 +293,7 @@
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
 
         DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
-                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+                PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
         assertTrue(request.requestId > 0);
         assertTrue(Objects.equals(sTemplateImsi1, request.template));
         assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -261,7 +317,7 @@
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
 
         DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
-                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+                PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
         assertTrue(request.requestId > 0);
         assertTrue(Objects.equals(sTemplateImsi1, request.template));
         assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -291,7 +347,7 @@
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
 
         DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
-                Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+                PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
         assertTrue(request.requestId > 0);
         assertTrue(Objects.equals(sTemplateImsi1, request.template));
         assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -322,7 +378,7 @@
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
 
         DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
-                UID_RED, NetworkStatsAccess.Level.DEFAULT);
+                PID_RED, UID_RED, PACKAGE_SYSTEM , NetworkStatsAccess.Level.DEFAULT);
         assertTrue(request.requestId > 0);
         assertTrue(Objects.equals(sTemplateImsi1, request.template));
         assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -355,7 +411,7 @@
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
 
         DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
-                UID_BLUE, NetworkStatsAccess.Level.DEFAULT);
+                PID_BLUE, UID_BLUE, PACKAGE_BLUE, NetworkStatsAccess.Level.DEFAULT);
         assertTrue(request.requestId > 0);
         assertTrue(Objects.equals(sTemplateImsi1, request.template));
         assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -387,7 +443,7 @@
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
 
         DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
-                UID_BLUE, NetworkStatsAccess.Level.USER);
+                PID_BLUE, UID_BLUE, PACKAGE_BLUE, NetworkStatsAccess.Level.USER);
         assertTrue(request.requestId > 0);
         assertTrue(Objects.equals(sTemplateImsi1, request.template));
         assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -420,7 +476,7 @@
                 DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
 
         DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
-                UID_RED, NetworkStatsAccess.Level.USER);
+                PID_RED, UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.USER);
         assertTrue(request.requestId > 0);
         assertTrue(Objects.equals(sTemplateImsi1, request.template));
         assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);