Merge "Skipping printing AssumptionViolatedExceptions."
diff --git a/Tethering/bpf_progs/offload.c b/Tethering/bpf_progs/offload.c
index ea64343..7f9754d 100644
--- a/Tethering/bpf_progs/offload.c
+++ b/Tethering/bpf_progs/offload.c
@@ -600,13 +600,7 @@
     return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */);
 }
 
-// Full featured (required) implementations for 5.8+ kernels
-
-DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_ether$5_8", AID_ROOT, AID_NETWORK_STACK,
-                     sched_cls_tether_downstream4_ether_5_8, KVER(5, 8, 0))
-(struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ true, /* downstream */ true, /* updatetime */ true);
-}
+// 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,
                      sched_cls_tether_downstream4_rawip_5_8, KVER(5, 8, 0))
@@ -614,28 +608,27 @@
     return do_forward4(skb, /* is_ethernet */ false, /* downstream */ true, /* updatetime */ true);
 }
 
-DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_ether$5_8", AID_ROOT, AID_NETWORK_STACK,
-                     sched_cls_tether_upstream4_ether_5_8, KVER(5, 8, 0))
-(struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ true, /* downstream */ false, /* updatetime */ true);
-}
-
 DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_rawip$5_8", AID_ROOT, AID_NETWORK_STACK,
                      sched_cls_tether_upstream4_rawip_5_8, KVER(5, 8, 0))
 (struct __sk_buff* skb) {
     return do_forward4(skb, /* is_ethernet */ false, /* downstream */ false, /* updatetime */ true);
 }
 
-// Full featured (optional) implementations for [4.14..5.8) kernels
-
-DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$opt",
-                                    AID_ROOT, AID_NETWORK_STACK,
-                                    sched_cls_tether_downstream4_ether_opt,
-                                    KVER(4, 14, 0), KVER(5, 8, 0))
+DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_ether$5_8", AID_ROOT, AID_NETWORK_STACK,
+                     sched_cls_tether_downstream4_ether_5_8, KVER(5, 8, 0))
 (struct __sk_buff* skb) {
     return do_forward4(skb, /* is_ethernet */ true, /* downstream */ true, /* updatetime */ true);
 }
 
+DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_ether$5_8", AID_ROOT, AID_NETWORK_STACK,
+                     sched_cls_tether_upstream4_ether_5_8, KVER(5, 8, 0))
+(struct __sk_buff* skb) {
+    return do_forward4(skb, /* is_ethernet */ true, /* downstream */ false, /* updatetime */ true);
+}
+
+// Full featured (optional) implementations for 4.14-S, 4.19-S & 5.4-S kernels
+// (optional, because we need to be able to fallback for 4.14/4.19/5.4 pre-S kernels)
+
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$opt",
                                     AID_ROOT, AID_NETWORK_STACK,
                                     sched_cls_tether_downstream4_rawip_opt,
@@ -644,14 +637,6 @@
     return do_forward4(skb, /* is_ethernet */ false, /* downstream */ true, /* updatetime */ true);
 }
 
-DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$opt",
-                                    AID_ROOT, AID_NETWORK_STACK,
-                                    sched_cls_tether_upstream4_ether_opt,
-                                    KVER(4, 14, 0), KVER(5, 8, 0))
-(struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ true, /* downstream */ false, /* updatetime */ true);
-}
-
 DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$opt",
                                     AID_ROOT, AID_NETWORK_STACK,
                                     sched_cls_tether_upstream4_rawip_opt,
@@ -660,8 +645,25 @@
     return do_forward4(skb, /* is_ethernet */ false, /* downstream */ false, /* updatetime */ true);
 }
 
+DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$opt",
+                                    AID_ROOT, AID_NETWORK_STACK,
+                                    sched_cls_tether_downstream4_ether_opt,
+                                    KVER(4, 14, 0), KVER(5, 8, 0))
+(struct __sk_buff* skb) {
+    return do_forward4(skb, /* is_ethernet */ true, /* downstream */ true, /* updatetime */ true);
+}
+
+DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$opt",
+                                    AID_ROOT, AID_NETWORK_STACK,
+                                    sched_cls_tether_upstream4_ether_opt,
+                                    KVER(4, 14, 0), KVER(5, 8, 0))
+(struct __sk_buff* skb) {
+    return do_forward4(skb, /* is_ethernet */ true, /* downstream */ false, /* updatetime */ true);
+}
+
 // Partial (TCP-only: will not update 'last_used' field) implementations for 4.14+ kernels.
-// These will be loaded only if the above optional ones failed (loading of *these* must succeed).
+// These will be loaded only if the above optional ones failed (loading of *these* must succeed
+// for 5.4+, since that is always an R patched kernel).
 //
 // [Note: as a result TCP connections will not have their conntrack timeout refreshed, however,
 // since /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established defaults to 432000 (seconds),
@@ -671,40 +673,73 @@
 // which enforces and documents the required kernel cherrypicks will make it pretty unlikely that
 // many devices upgrading to S will end up relying on these fallback programs.
 
+// RAWIP: Required for 5.4-R kernels -- which always support bpf_skb_change_head().
+
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$5_4", AID_ROOT, AID_NETWORK_STACK,
+                           sched_cls_tether_downstream4_rawip_5_4, KVER(5, 4, 0), KVER(5, 8, 0))
+(struct __sk_buff* skb) {
+    return do_forward4(skb, /* is_ethernet */ false, /* downstream */ true, /* updatetime */ false);
+}
+
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$5_4", AID_ROOT, AID_NETWORK_STACK,
+                           sched_cls_tether_upstream4_rawip_5_4, KVER(5, 4, 0), KVER(5, 8, 0))
+(struct __sk_buff* skb) {
+    return do_forward4(skb, /* is_ethernet */ false, /* downstream */ false, /* updatetime */ false);
+}
+
+// RAWIP: Optional for 4.14/4.19 (R) kernels -- which support bpf_skb_change_head().
+// [Note: fallback for 4.14/4.19 (P/Q) kernels is below in stub section]
+
+DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$4_14",
+                                    AID_ROOT, AID_NETWORK_STACK,
+                                    sched_cls_tether_downstream4_rawip_4_14,
+                                    KVER(4, 14, 0), KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+    return do_forward4(skb, /* is_ethernet */ false, /* downstream */ true, /* updatetime */ false);
+}
+
+DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$4_14",
+                                    AID_ROOT, AID_NETWORK_STACK,
+                                    sched_cls_tether_upstream4_rawip_4_14,
+                                    KVER(4, 14, 0), KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+    return do_forward4(skb, /* is_ethernet */ false, /* downstream */ false, /* updatetime */ false);
+}
+
+// ETHER: Required for 4.14-Q/R, 4.19-Q/R & 5.4-R kernels.
+
 DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$4_14", AID_ROOT, AID_NETWORK_STACK,
                            sched_cls_tether_downstream4_ether_4_14, KVER(4, 14, 0), KVER(5, 8, 0))
 (struct __sk_buff* skb) {
     return do_forward4(skb, /* is_ethernet */ true, /* downstream */ true, /* updatetime */ false);
 }
 
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$4_14", AID_ROOT, AID_NETWORK_STACK,
-                           sched_cls_tether_downstream4_rawip_4_14, KVER(4, 14, 0), KVER(5, 8, 0))
-(struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ false, /* downstream */ true, /* updatetime */ false);
-}
-
 DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$4_14", AID_ROOT, AID_NETWORK_STACK,
                            sched_cls_tether_upstream4_ether_4_14, KVER(4, 14, 0), KVER(5, 8, 0))
 (struct __sk_buff* skb) {
     return do_forward4(skb, /* is_ethernet */ true, /* downstream */ false, /* updatetime */ false);
 }
 
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$4_14", AID_ROOT, AID_NETWORK_STACK,
-                           sched_cls_tether_upstream4_rawip_4_14, KVER(4, 14, 0), KVER(5, 8, 0))
-(struct __sk_buff* skb) {
-    return do_forward4(skb, /* is_ethernet */ false, /* downstream */ false, /* updatetime */ false);
-}
+// Placeholder (no-op) implementations for older Q kernels
 
-// Placeholder (no-op) implementations for older pre-4.14 kernels
+// RAWIP: 4.9-P/Q, 4.14-P/Q & 4.19-Q kernels -- without bpf_skb_change_head() for tc programs
 
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$stub", AID_ROOT, AID_NETWORK_STACK,
-                           sched_cls_tether_downstream4_ether_stub, KVER_NONE, KVER(4, 14, 0))
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$stub", AID_ROOT, AID_NETWORK_STACK,
+                           sched_cls_tether_downstream4_rawip_stub, KVER_NONE, KVER(5, 4, 0))
 (struct __sk_buff* skb) {
     return TC_ACT_OK;
 }
 
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$stub", AID_ROOT, AID_NETWORK_STACK,
-                           sched_cls_tether_downstream4_rawip_stub, KVER_NONE, KVER(4, 14, 0))
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$stub", AID_ROOT, AID_NETWORK_STACK,
+                           sched_cls_tether_upstream4_rawip_stub, KVER_NONE, KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+    return TC_ACT_OK;
+}
+
+// ETHER: 4.9-P/Q kernel
+
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$stub", AID_ROOT, AID_NETWORK_STACK,
+                           sched_cls_tether_downstream4_ether_stub, KVER_NONE, KVER(4, 14, 0))
 (struct __sk_buff* skb) {
     return TC_ACT_OK;
 }
@@ -715,35 +750,74 @@
     return TC_ACT_OK;
 }
 
-DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$stub", AID_ROOT, AID_NETWORK_STACK,
-                           sched_cls_tether_upstream4_rawip_stub, KVER_NONE, KVER(4, 14, 0))
-(struct __sk_buff* skb) {
-    return TC_ACT_OK;
+// ----- XDP Support -----
+
+DEFINE_BPF_MAP_GRW(tether_xdp_devmap, DEVMAP_HASH, uint32_t, uint32_t, 64,
+                   AID_NETWORK_STACK)
+
+static inline __always_inline int do_xdp_forward6(struct xdp_md *ctx, const bool is_ethernet,
+        const bool downstream) {
+    return XDP_PASS;
 }
 
-// ----- XDP Support -----
+static inline __always_inline int do_xdp_forward4(struct xdp_md *ctx, const bool is_ethernet,
+        const bool downstream) {
+    return XDP_PASS;
+}
+
+static inline __always_inline int do_xdp_forward_ether(struct xdp_md *ctx, const bool downstream) {
+    const void* data = (void*)(long)ctx->data;
+    const void* data_end = (void*)(long)ctx->data_end;
+    const struct ethhdr* eth = data;
+
+    // Make sure we actually have an ethernet header
+    if ((void*)(eth + 1) > data_end) return XDP_PASS;
+
+    if (eth->h_proto == htons(ETH_P_IPV6))
+        return do_xdp_forward6(ctx, /* is_ethernet */ true, downstream);
+    if (eth->h_proto == htons(ETH_P_IP))
+        return do_xdp_forward4(ctx, /* is_ethernet */ true, downstream);
+
+    // Anything else we don't know how to handle...
+    return XDP_PASS;
+}
+
+static inline __always_inline int do_xdp_forward_rawip(struct xdp_md *ctx, const bool downstream) {
+    const void* data = (void*)(long)ctx->data;
+    const void* data_end = (void*)(long)ctx->data_end;
+
+    // The top nibble of both IPv4 and IPv6 headers is the IP version.
+    if (data_end - data < 1) return XDP_PASS;
+    const uint8_t v = (*(uint8_t*)data) >> 4;
+
+    if (v == 6) return do_xdp_forward6(ctx, /* is_ethernet */ false, downstream);
+    if (v == 4) return do_xdp_forward4(ctx, /* is_ethernet */ false, downstream);
+
+    // Anything else we don't know how to handle...
+    return XDP_PASS;
+}
 
 #define DEFINE_XDP_PROG(str, func) \
     DEFINE_BPF_PROG_KVER(str, AID_ROOT, AID_NETWORK_STACK, func, KVER(5, 9, 0))(struct xdp_md *ctx)
 
 DEFINE_XDP_PROG("xdp/tether_downstream_ether",
                  xdp_tether_downstream_ether) {
-    return XDP_PASS;
+    return do_xdp_forward_ether(ctx, /* downstream */ true);
 }
 
 DEFINE_XDP_PROG("xdp/tether_downstream_rawip",
                  xdp_tether_downstream_rawip) {
-    return XDP_PASS;
+    return do_xdp_forward_rawip(ctx, /* downstream */ true);
 }
 
 DEFINE_XDP_PROG("xdp/tether_upstream_ether",
                  xdp_tether_upstream_ether) {
-    return XDP_PASS;
+    return do_xdp_forward_ether(ctx, /* downstream */ false);
 }
 
 DEFINE_XDP_PROG("xdp/tether_upstream_rawip",
                  xdp_tether_upstream_rawip) {
-    return XDP_PASS;
+    return do_xdp_forward_rawip(ctx, /* downstream */ false);
 }
 
 LICENSE("Apache 2.0");
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java
new file mode 100644
index 0000000..3636b03
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TestConnectivityManager.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.tethering;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.os.Handler;
+import android.os.UserHandle;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Simulates upstream switching and sending NetworkCallbacks and CONNECTIVITY_ACTION broadcasts.
+ *
+ * Unlike any real networking code, this class is single-threaded and entirely synchronous.
+ * The effects of all method calls (including sending fake broadcasts, sending callbacks, etc.) are
+ * performed immediately on the caller's thread before returning.
+ *
+ * TODO: this duplicates a fair amount of code from ConnectivityManager and ConnectivityService.
+ * Consider using a ConnectivityService object instead, as used in ConnectivityServiceTest.
+ *
+ * Things to consider:
+ * - ConnectivityService uses a real handler for realism, and these test use TestLooper (or even
+ *   invoke callbacks directly inline) for determinism. Using a real ConnectivityService would
+ *   require adding dispatchAll() calls and migrating to handlers.
+ * - ConnectivityService does not provide a way to order CONNECTIVITY_ACTION before or after the
+ *   NetworkCallbacks for the same network change. That ability is useful because the upstream
+ *   selection code in Tethering is vulnerable to race conditions, due to its reliance on multiple
+ *   separate NetworkCallbacks and BroadcastReceivers, each of which trigger different types of
+ *   updates. If/when the upstream selection code is refactored to a more level-triggered model
+ *   (e.g., with an idempotent function that takes into account all state every time any part of
+ *   that state changes), this may become less important or unnecessary.
+ */
+public class TestConnectivityManager extends ConnectivityManager {
+    public Map<NetworkCallback, Handler> allCallbacks = new HashMap<>();
+    public Set<NetworkCallback> trackingDefault = new HashSet<>();
+    public TestNetworkAgent defaultNetwork = null;
+    public Map<NetworkCallback, NetworkRequest> listening = new HashMap<>();
+    public Map<NetworkCallback, NetworkRequest> requested = new HashMap<>();
+    public Map<NetworkCallback, Integer> legacyTypeMap = new HashMap<>();
+
+    private final NetworkRequest mDefaultRequest;
+    private final Context mContext;
+
+    private int mNetworkId = 100;
+
+    /**
+     * Constructs a TestConnectivityManager.
+     * @param ctx the context to use. Must be a fake or a mock because otherwise the test will
+     *            attempt to send real broadcasts and resulting in permission denials.
+     * @param svc an IConnectivityManager. Should be a fake or a mock.
+     * @param defaultRequest the default NetworkRequest that will be used by Tethering.
+     */
+    public TestConnectivityManager(Context ctx, IConnectivityManager svc,
+            NetworkRequest defaultRequest) {
+        super(ctx, svc);
+        mContext = ctx;
+        mDefaultRequest = defaultRequest;
+    }
+
+    boolean hasNoCallbacks() {
+        return allCallbacks.isEmpty()
+                && trackingDefault.isEmpty()
+                && listening.isEmpty()
+                && requested.isEmpty()
+                && legacyTypeMap.isEmpty();
+    }
+
+    boolean onlyHasDefaultCallbacks() {
+        return (allCallbacks.size() == 1)
+                && (trackingDefault.size() == 1)
+                && listening.isEmpty()
+                && requested.isEmpty()
+                && legacyTypeMap.isEmpty();
+    }
+
+    boolean isListeningForAll() {
+        final NetworkCapabilities empty = new NetworkCapabilities();
+        empty.clearAll();
+
+        for (NetworkRequest req : listening.values()) {
+            if (req.networkCapabilities.equalRequestableCapabilities(empty)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    int getNetworkId() {
+        return ++mNetworkId;
+    }
+
+    void makeDefaultNetwork(TestNetworkAgent agent) {
+        if (Objects.equals(defaultNetwork, agent)) return;
+
+        final TestNetworkAgent formerDefault = defaultNetwork;
+        defaultNetwork = agent;
+
+        if (formerDefault != null) {
+            sendConnectivityAction(formerDefault.legacyType, false /* connected */);
+        }
+        if (defaultNetwork != null) {
+            sendConnectivityAction(defaultNetwork.legacyType, true /* connected */);
+        }
+
+        for (NetworkCallback cb : trackingDefault) {
+            if (defaultNetwork != null) {
+                cb.onAvailable(defaultNetwork.networkId);
+                cb.onCapabilitiesChanged(
+                        defaultNetwork.networkId, defaultNetwork.networkCapabilities);
+                cb.onLinkPropertiesChanged(
+                        defaultNetwork.networkId, defaultNetwork.linkProperties);
+            }
+        }
+    }
+
+    @Override
+    public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) {
+        assertFalse(allCallbacks.containsKey(cb));
+        allCallbacks.put(cb, h);
+        if (mDefaultRequest.equals(req)) {
+            assertFalse(trackingDefault.contains(cb));
+            trackingDefault.add(cb);
+        } else {
+            assertFalse(requested.containsKey(cb));
+            requested.put(cb, req);
+        }
+    }
+
+    @Override
+    public void requestNetwork(NetworkRequest req, NetworkCallback cb) {
+        fail("Should never be called.");
+    }
+
+    @Override
+    public void requestNetwork(NetworkRequest req,
+            int timeoutMs, int legacyType, Handler h, NetworkCallback cb) {
+        assertFalse(allCallbacks.containsKey(cb));
+        allCallbacks.put(cb, h);
+        assertFalse(requested.containsKey(cb));
+        requested.put(cb, req);
+        assertFalse(legacyTypeMap.containsKey(cb));
+        if (legacyType != ConnectivityManager.TYPE_NONE) {
+            legacyTypeMap.put(cb, legacyType);
+        }
+    }
+
+    @Override
+    public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb, Handler h) {
+        assertFalse(allCallbacks.containsKey(cb));
+        allCallbacks.put(cb, h);
+        assertFalse(listening.containsKey(cb));
+        listening.put(cb, req);
+    }
+
+    @Override
+    public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb) {
+        fail("Should never be called.");
+    }
+
+    @Override
+    public void registerDefaultNetworkCallback(NetworkCallback cb, Handler h) {
+        fail("Should never be called.");
+    }
+
+    @Override
+    public void registerDefaultNetworkCallback(NetworkCallback cb) {
+        fail("Should never be called.");
+    }
+
+    @Override
+    public void unregisterNetworkCallback(NetworkCallback cb) {
+        if (trackingDefault.contains(cb)) {
+            trackingDefault.remove(cb);
+        } else if (listening.containsKey(cb)) {
+            listening.remove(cb);
+        } else if (requested.containsKey(cb)) {
+            requested.remove(cb);
+            legacyTypeMap.remove(cb);
+        } else {
+            fail("Unexpected callback removed");
+        }
+        allCallbacks.remove(cb);
+
+        assertFalse(allCallbacks.containsKey(cb));
+        assertFalse(trackingDefault.contains(cb));
+        assertFalse(listening.containsKey(cb));
+        assertFalse(requested.containsKey(cb));
+    }
+
+    private void sendConnectivityAction(int type, boolean connected) {
+        NetworkInfo ni = new NetworkInfo(type, 0 /* subtype */,  getNetworkTypeName(type),
+                "" /* subtypeName */);
+        NetworkInfo.DetailedState state = connected
+                ? NetworkInfo.DetailedState.CONNECTED
+                : NetworkInfo.DetailedState.DISCONNECTED;
+        ni.setDetailedState(state, "" /* reason */, "" /* extraInfo */);
+        Intent intent = new Intent(CONNECTIVITY_ACTION);
+        intent.putExtra(EXTRA_NETWORK_INFO, ni);
+        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    public static class TestNetworkAgent {
+        public final TestConnectivityManager cm;
+        public final Network networkId;
+        public final NetworkCapabilities networkCapabilities;
+        public final LinkProperties linkProperties;
+        // TODO: delete when tethering no longer uses CONNECTIVITY_ACTION.
+        public final int legacyType;
+
+        public TestNetworkAgent(TestConnectivityManager cm, NetworkCapabilities nc) {
+            this.cm = cm;
+            this.networkId = new Network(cm.getNetworkId());
+            networkCapabilities = copy(nc);
+            linkProperties = new LinkProperties();
+            legacyType = toLegacyType(nc);
+        }
+
+        public TestNetworkAgent(TestConnectivityManager cm, UpstreamNetworkState state) {
+            this.cm = cm;
+            networkId = state.network;
+            networkCapabilities = state.networkCapabilities;
+            linkProperties = state.linkProperties;
+            this.legacyType = toLegacyType(networkCapabilities);
+        }
+
+        private static int toLegacyType(NetworkCapabilities nc) {
+            for (int type = 0; type < ConnectivityManager.TYPE_TEST; type++) {
+                if (matchesLegacyType(nc, type)) return type;
+            }
+            throw new IllegalArgumentException(("Can't determine legacy type for: ") + nc);
+        }
+
+        private static boolean matchesLegacyType(NetworkCapabilities nc, int legacyType) {
+            final NetworkCapabilities typeNc;
+            try {
+                typeNc = ConnectivityManager.networkCapabilitiesForType(legacyType);
+            } catch (IllegalArgumentException e) {
+                // networkCapabilitiesForType does not support all legacy types.
+                return false;
+            }
+            return typeNc.satisfiedByNetworkCapabilities(nc);
+        }
+
+        private boolean matchesLegacyType(int legacyType) {
+            return matchesLegacyType(networkCapabilities, legacyType);
+        }
+
+        public void fakeConnect() {
+            for (NetworkRequest request : cm.requested.values()) {
+                if (matchesLegacyType(request.legacyType)) {
+                    cm.sendConnectivityAction(legacyType, true /* connected */);
+                    // In practice, a given network can match only one legacy type.
+                    break;
+                }
+            }
+            for (NetworkCallback cb : cm.listening.keySet()) {
+                cb.onAvailable(networkId);
+                cb.onCapabilitiesChanged(networkId, copy(networkCapabilities));
+                cb.onLinkPropertiesChanged(networkId, copy(linkProperties));
+            }
+        }
+
+        public void fakeDisconnect() {
+            for (NetworkRequest request : cm.requested.values()) {
+                if (matchesLegacyType(request.legacyType)) {
+                    cm.sendConnectivityAction(legacyType, false /* connected */);
+                    break;
+                }
+            }
+            for (NetworkCallback cb : cm.listening.keySet()) {
+                cb.onLost(networkId);
+            }
+        }
+
+        public void sendLinkProperties() {
+            for (NetworkCallback cb : cm.listening.keySet()) {
+                cb.onLinkPropertiesChanged(networkId, copy(linkProperties));
+            }
+        }
+
+        @Override
+        public String toString() {
+            return String.format("TestNetworkAgent: %s %s", networkId, networkCapabilities);
+        }
+    }
+
+    static NetworkCapabilities copy(NetworkCapabilities nc) {
+        return new NetworkCapabilities(nc);
+    }
+
+    static LinkProperties copy(LinkProperties lp) {
+        return new LinkProperties(lp);
+    }
+}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index c5a43b7..40b7c55 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -25,6 +25,9 @@
 import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
+import static android.net.ConnectivityManager.TYPE_NONE;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
@@ -64,6 +67,7 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.argThat;
@@ -72,6 +76,7 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -94,10 +99,11 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.hardware.usb.UsbManager;
-import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
 import android.net.EthernetManager;
 import android.net.EthernetManager.TetheredInterfaceCallback;
 import android.net.EthernetManager.TetheredInterfaceRequest;
+import android.net.IConnectivityManager;
 import android.net.IIntResultListener;
 import android.net.INetd;
 import android.net.ITetheringEventCallback;
@@ -200,6 +206,10 @@
     private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
     private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app";
 
+    private static final int CELLULAR_NETID = 100;
+    private static final int WIFI_NETID = 101;
+    private static final int DUN_NETID = 102;
+
     private static final int DHCPSERVER_START_TIMEOUT_MS = 1000;
 
     @Mock private ApplicationInfo mApplicationInfo;
@@ -212,7 +222,6 @@
     @Mock private UsbManager mUsbManager;
     @Mock private WifiManager mWifiManager;
     @Mock private CarrierConfigManager mCarrierConfigManager;
-    @Mock private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
     @Mock private IPv6TetheringCoordinator mIPv6TetheringCoordinator;
     @Mock private DadProxy mDadProxy;
     @Mock private RouterAdvertisementDaemon mRouterAdvertisementDaemon;
@@ -220,8 +229,6 @@
     @Mock private IDhcpServer mDhcpServer;
     @Mock private INetd mNetd;
     @Mock private UserManager mUserManager;
-    @Mock private NetworkRequest mNetworkRequest;
-    @Mock private ConnectivityManager mCm;
     @Mock private EthernetManager mEm;
     @Mock private TetheringNotificationUpdater mNotificationUpdater;
     @Mock private BpfCoordinator mBpfCoordinator;
@@ -249,6 +256,11 @@
     private OffloadController mOffloadCtrl;
     private PrivateAddressCoordinator mPrivateAddressCoordinator;
     private SoftApCallback mSoftApCallback;
+    private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
+
+    private TestConnectivityManager mCm;
+    private NetworkRequest mNetworkRequest;
+    private NetworkCallback mDefaultNetworkCallback;
 
     private class TestContext extends BroadcastInterceptingContext {
         TestContext(Context base) {
@@ -400,7 +412,10 @@
         @Override
         public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx,
                 StateMachine target, SharedLog log, int what) {
+            // Use a real object instead of a mock so that some tests can use a real UNM and some
+            // can use a mock.
             mUpstreamNetworkMonitorSM = target;
+            mUpstreamNetworkMonitor = spy(super.getUpstreamNetworkMonitor(ctx, target, log, what));
             return mUpstreamNetworkMonitor;
         }
 
@@ -480,15 +495,15 @@
         }
     }
 
-    private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4,
-            boolean withIPv6, boolean with464xlat) {
+    private static LinkProperties buildUpstreamLinkProperties(String interfaceName,
+            boolean withIPv4, boolean withIPv6, boolean with464xlat) {
         final LinkProperties prop = new LinkProperties();
-        prop.setInterfaceName(TEST_MOBILE_IFNAME);
+        prop.setInterfaceName(interfaceName);
 
         if (withIPv4) {
             prop.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
                     InetAddresses.parseNumericAddress("10.0.0.1"),
-                    TEST_MOBILE_IFNAME, RTN_UNICAST));
+                    interfaceName, RTN_UNICAST));
         }
 
         if (withIPv6) {
@@ -498,23 +513,40 @@
                             NetworkConstants.RFC7421_PREFIX_LENGTH));
             prop.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0),
                     InetAddresses.parseNumericAddress("2001:db8::1"),
-                    TEST_MOBILE_IFNAME, RTN_UNICAST));
+                    interfaceName, RTN_UNICAST));
         }
 
         if (with464xlat) {
+            final String clatInterface = "v4-" + interfaceName;
             final LinkProperties stackedLink = new LinkProperties();
-            stackedLink.setInterfaceName(TEST_XLAT_MOBILE_IFNAME);
+            stackedLink.setInterfaceName(clatInterface);
             stackedLink.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
                     InetAddresses.parseNumericAddress("192.0.0.1"),
-                    TEST_XLAT_MOBILE_IFNAME, RTN_UNICAST));
+                    clatInterface, RTN_UNICAST));
 
             prop.addStackedLink(stackedLink);
         }
 
+        return prop;
+    }
 
-        final NetworkCapabilities capabilities = new NetworkCapabilities()
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-        return new UpstreamNetworkState(prop, capabilities, new Network(100));
+    private static NetworkCapabilities buildUpstreamCapabilities(int transport, int... otherCaps) {
+        // TODO: add NOT_VCN_MANAGED.
+        final NetworkCapabilities nc = new NetworkCapabilities()
+                .addTransportType(transport)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        for (int cap : otherCaps) {
+            nc.addCapability(cap);
+        }
+        return nc;
+    }
+
+    private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4,
+            boolean withIPv6, boolean with464xlat) {
+        return new UpstreamNetworkState(
+                buildUpstreamLinkProperties(TEST_MOBILE_IFNAME, withIPv4, withIPv6, with464xlat),
+                buildUpstreamCapabilities(TRANSPORT_CELLULAR),
+                new Network(CELLULAR_NETID));
     }
 
     private static UpstreamNetworkState buildMobileIPv4UpstreamState() {
@@ -533,6 +565,22 @@
         return buildMobileUpstreamState(false, true, true);
     }
 
+    private static UpstreamNetworkState buildWifiUpstreamState() {
+        return new UpstreamNetworkState(
+                buildUpstreamLinkProperties(TEST_WIFI_IFNAME, true /* IPv4 */, true /* IPv6 */,
+                        false /* 464xlat */),
+                buildUpstreamCapabilities(TRANSPORT_WIFI),
+                new Network(WIFI_NETID));
+    }
+
+    private static UpstreamNetworkState buildDunUpstreamState() {
+        return new UpstreamNetworkState(
+                buildUpstreamLinkProperties(TEST_MOBILE_IFNAME, true /* IPv4 */, true /* IPv6 */,
+                        false /* 464xlat */),
+                buildUpstreamCapabilities(TRANSPORT_CELLULAR, NET_CAPABILITY_DUN),
+                new Network(DUN_NETID));
+    }
+
     // See FakeSettingsProvider#clearSettingsProvider() that this needs to be called before and
     // after use.
     @BeforeClass
@@ -578,9 +626,22 @@
         };
         mServiceContext.registerReceiver(mBroadcastReceiver,
                 new IntentFilter(ACTION_TETHER_STATE_CHANGED));
+
+        // TODO: add NOT_VCN_MANAGED here, but more importantly in the production code.
+        // TODO: even better, change TetheringDependencies.getDefaultNetworkRequest() to use
+        // registerSystemDefaultNetworkCallback() on S and above.
+        NetworkCapabilities defaultCaps = new NetworkCapabilities()
+                .addCapability(NET_CAPABILITY_INTERNET);
+        mNetworkRequest = new NetworkRequest(defaultCaps, TYPE_NONE, 1 /* requestId */,
+                NetworkRequest.Type.REQUEST);
+        mCm = spy(new TestConnectivityManager(mServiceContext, mock(IConnectivityManager.class),
+                mNetworkRequest));
+
         mTethering = makeTethering();
         verify(mStatsManager, times(1)).registerNetworkStatsProvider(anyString(), any());
         verify(mNetd).registerUnsolicitedEventListener(any());
+        verifyDefaultNetworkRequestFiled();
+
         final ArgumentCaptor<PhoneStateListener> phoneListenerCaptor =
                 ArgumentCaptor.forClass(PhoneStateListener.class);
         verify(mTelephonyManager).listen(phoneListenerCaptor.capture(),
@@ -617,8 +678,8 @@
     }
 
     private void initTetheringUpstream(UpstreamNetworkState upstreamState) {
-        when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState);
-        when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any())).thenReturn(upstreamState);
+        doReturn(upstreamState).when(mUpstreamNetworkMonitor).getCurrentPreferredUpstream();
+        doReturn(upstreamState).when(mUpstreamNetworkMonitor).selectPreferredUpstreamType(any());
     }
 
     private Tethering makeTethering() {
@@ -705,6 +766,19 @@
         mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
+    private void verifyDefaultNetworkRequestFiled() {
+        ArgumentCaptor<NetworkCallback> captor = ArgumentCaptor.forClass(NetworkCallback.class);
+        verify(mCm, times(1)).requestNetwork(eq(mNetworkRequest),
+                captor.capture(), any(Handler.class));
+        mDefaultNetworkCallback = captor.getValue();
+        assertNotNull(mDefaultNetworkCallback);
+
+        // The default network request is only ever filed once.
+        verifyNoMoreInteractions(mCm);
+        mUpstreamNetworkMonitor.startTrackDefaultNetwork(mNetworkRequest, mEntitleMgr);
+        verifyNoMoreInteractions(mCm);
+    }
+
     private void verifyInterfaceServingModeStarted(String ifname) throws Exception {
         verify(mNetd, times(1)).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
         verify(mNetd, times(1)).tetherInterfaceAdd(ifname);
@@ -1723,12 +1797,12 @@
     }
 
     private void setDataSaverEnabled(boolean enabled) {
-        final Intent intent = new Intent(ACTION_RESTRICT_BACKGROUND_CHANGED);
-        mServiceContext.sendBroadcastAsUser(intent, UserHandle.ALL);
-
         final int status = enabled ? RESTRICT_BACKGROUND_STATUS_ENABLED
                 : RESTRICT_BACKGROUND_STATUS_DISABLED;
-        when(mCm.getRestrictBackgroundStatus()).thenReturn(status);
+        doReturn(status).when(mCm).getRestrictBackgroundStatus();
+
+        final Intent intent = new Intent(ACTION_RESTRICT_BACKGROUND_CHANGED);
+        mServiceContext.sendBroadcastAsUser(intent, UserHandle.ALL);
         mLooper.dispatchAll();
     }
 
@@ -1882,7 +1956,8 @@
         // Verify that onUpstreamCapabilitiesChanged won't be called if not current upstream network
         // capabilities changed.
         final UpstreamNetworkState upstreamState2 = new UpstreamNetworkState(
-                upstreamState.linkProperties, upstreamState.networkCapabilities, new Network(101));
+                upstreamState.linkProperties, upstreamState.networkCapabilities,
+                new Network(WIFI_NETID));
         stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState2);
         verify(mNotificationUpdater, never()).onUpstreamCapabilitiesChanged(any());
     }
@@ -1992,7 +2067,7 @@
     public void testHandleIpConflict() throws Exception {
         final Network wifiNetwork = new Network(200);
         final Network[] allNetworks = { wifiNetwork };
-        when(mCm.getAllNetworks()).thenReturn(allNetworks);
+        doReturn(allNetworks).when(mCm).getAllNetworks();
         runUsbTethering(null);
         final ArgumentCaptor<InterfaceConfigurationParcel> ifaceConfigCaptor =
                 ArgumentCaptor.forClass(InterfaceConfigurationParcel.class);
@@ -2019,7 +2094,7 @@
         final Network btNetwork = new Network(201);
         final Network mobileNetwork = new Network(202);
         final Network[] allNetworks = { wifiNetwork, btNetwork, mobileNetwork };
-        when(mCm.getAllNetworks()).thenReturn(allNetworks);
+        doReturn(allNetworks).when(mCm).getAllNetworks();
         runUsbTethering(null);
         verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
                 any(), any());
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
index 232588c..7d735fc 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
@@ -29,7 +29,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
@@ -48,7 +47,6 @@
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
-import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.util.SharedLog;
@@ -60,6 +58,7 @@
 
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent;
 
 import org.junit.After;
 import org.junit.Before;
@@ -71,10 +70,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
@@ -89,6 +85,13 @@
     // any specific TRANSPORT_* is sufficient to identify this request.
     private static final NetworkRequest sDefaultRequest = new NetworkRequest.Builder().build();
 
+    private static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities.Builder()
+            .addTransportType(TRANSPORT_CELLULAR).addCapability(NET_CAPABILITY_INTERNET).build();
+    private static final NetworkCapabilities DUN_CAPABILITIES = new NetworkCapabilities.Builder()
+            .addTransportType(TRANSPORT_CELLULAR).addCapability(NET_CAPABILITY_DUN).build();
+    private static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities.Builder()
+            .addTransportType(TRANSPORT_WIFI).addCapability(NET_CAPABILITY_INTERNET).build();
+
     @Mock private Context mContext;
     @Mock private EntitlementManager mEntitleMgr;
     @Mock private IConnectivityManager mCS;
@@ -106,7 +109,7 @@
         when(mLog.forSubComponent(anyString())).thenReturn(mLog);
         when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
 
-        mCM = spy(new TestConnectivityManager(mContext, mCS));
+        mCM = spy(new TestConnectivityManager(mContext, mCS, sDefaultRequest));
         mSM = new TestStateMachine();
         mUNM = new UpstreamNetworkMonitor(
                 (ConnectivityManager) mCM, mSM, mLog, EVENT_UNM_UPDATE);
@@ -292,7 +295,7 @@
         // There are no networks, so there is nothing to select.
         assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
 
-        final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
+        final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, WIFI_CAPABILITIES);
         wifiAgent.fakeConnect();
         // WiFi is up, we should prefer it.
         assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
@@ -300,7 +303,7 @@
         // There are no networks, so there is nothing to select.
         assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
 
-        final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+        final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, CELL_CAPABILITIES);
         cellAgent.fakeConnect();
         assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
 
@@ -341,8 +344,7 @@
         mUNM.updateMobileRequiresDun(true);
         assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
 
-        final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
-        dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
+        final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, DUN_CAPABILITIES);
         dunAgent.fakeConnect();
 
         // WiFi is still preferred.
@@ -374,7 +376,7 @@
         mUNM.updateMobileRequiresDun(false);
 
         // [0] Mobile connects, DUN not required -> mobile selected.
-        final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+        final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, CELL_CAPABILITIES);
         cellAgent.fakeConnect();
         mCM.makeDefaultNetwork(cellAgent);
         assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
@@ -385,7 +387,7 @@
         when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
 
         // [2] WiFi connects but not validated/promoted to default -> mobile selected.
-        final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
+        final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, WIFI_CAPABILITIES);
         wifiAgent.fakeConnect();
         assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
 
@@ -405,7 +407,7 @@
         // into UNM we should test for this here.
 
         // [6] DUN network arrives -> DUN selected
-        final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+        final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, CELL_CAPABILITIES);
         dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
         dunAgent.networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET);
         dunAgent.fakeConnect();
@@ -428,7 +430,7 @@
         final Set<String> alreadySeen = new HashSet<>();
 
         // [1] Pretend Wi-Fi connects.
-        final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
+        final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, WIFI_CAPABILITIES);
         final LinkProperties wifiLp = wifiAgent.linkProperties;
         wifiLp.setInterfaceName("wlan0");
         final String[] wifi_addrs = {
@@ -455,7 +457,7 @@
         assertEquals(alreadySeen.size(), local.size());
 
         // [2] Pretend mobile connects.
-        final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+        final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, CELL_CAPABILITIES);
         final LinkProperties cellLp = cellAgent.linkProperties;
         cellLp.setInterfaceName("rmnet_data0");
         final String[] cell_addrs = {
@@ -476,9 +478,7 @@
         assertEquals(alreadySeen.size(), local.size());
 
         // [3] Pretend DUN connects.
-        final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
-        dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
-        dunAgent.networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET);
+        final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, DUN_CAPABILITIES);
         final LinkProperties dunLp = dunAgent.linkProperties;
         dunLp.setInterfaceName("rmnet_data1");
         final String[] dun_addrs = {
@@ -528,11 +528,11 @@
         mUNM.startTrackDefaultNetwork(sDefaultRequest, mEntitleMgr);
         mUNM.startObserveAllNetworks();
         // Setup wifi and make wifi as default network.
-        final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
+        final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, WIFI_CAPABILITIES);
         wifiAgent.fakeConnect();
         mCM.makeDefaultNetwork(wifiAgent);
         // Setup mobile network.
-        final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+        final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, CELL_CAPABILITIES);
         cellAgent.fakeConnect();
 
         assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI,
@@ -567,187 +567,6 @@
         return false;
     }
 
-    public static class TestConnectivityManager extends ConnectivityManager {
-        public Map<NetworkCallback, Handler> allCallbacks = new HashMap<>();
-        public Set<NetworkCallback> trackingDefault = new HashSet<>();
-        public TestNetworkAgent defaultNetwork = null;
-        public Map<NetworkCallback, NetworkRequest> listening = new HashMap<>();
-        public Map<NetworkCallback, NetworkRequest> requested = new HashMap<>();
-        public Map<NetworkCallback, Integer> legacyTypeMap = new HashMap<>();
-
-        private int mNetworkId = 100;
-
-        public TestConnectivityManager(Context ctx, IConnectivityManager svc) {
-            super(ctx, svc);
-        }
-
-        boolean hasNoCallbacks() {
-            return allCallbacks.isEmpty()
-                    && trackingDefault.isEmpty()
-                    && listening.isEmpty()
-                    && requested.isEmpty()
-                    && legacyTypeMap.isEmpty();
-        }
-
-        boolean onlyHasDefaultCallbacks() {
-            return (allCallbacks.size() == 1)
-                    && (trackingDefault.size() == 1)
-                    && listening.isEmpty()
-                    && requested.isEmpty()
-                    && legacyTypeMap.isEmpty();
-        }
-
-        boolean isListeningForAll() {
-            final NetworkCapabilities empty = new NetworkCapabilities();
-            empty.clearAll();
-
-            for (NetworkRequest req : listening.values()) {
-                if (req.networkCapabilities.equalRequestableCapabilities(empty)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        int getNetworkId() {
-            return ++mNetworkId;
-        }
-
-        void makeDefaultNetwork(TestNetworkAgent agent) {
-            if (Objects.equals(defaultNetwork, agent)) return;
-
-            final TestNetworkAgent formerDefault = defaultNetwork;
-            defaultNetwork = agent;
-
-            for (NetworkCallback cb : trackingDefault) {
-                if (defaultNetwork != null) {
-                    cb.onAvailable(defaultNetwork.networkId);
-                    cb.onCapabilitiesChanged(
-                            defaultNetwork.networkId, defaultNetwork.networkCapabilities);
-                    cb.onLinkPropertiesChanged(
-                            defaultNetwork.networkId, defaultNetwork.linkProperties);
-                }
-            }
-        }
-
-        @Override
-        public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) {
-            assertFalse(allCallbacks.containsKey(cb));
-            allCallbacks.put(cb, h);
-            if (sDefaultRequest.equals(req)) {
-                assertFalse(trackingDefault.contains(cb));
-                trackingDefault.add(cb);
-            } else {
-                assertFalse(requested.containsKey(cb));
-                requested.put(cb, req);
-            }
-        }
-
-        @Override
-        public void requestNetwork(NetworkRequest req, NetworkCallback cb) {
-            fail("Should never be called.");
-        }
-
-        @Override
-        public void requestNetwork(NetworkRequest req,
-                int timeoutMs, int legacyType, Handler h, NetworkCallback cb) {
-            assertFalse(allCallbacks.containsKey(cb));
-            allCallbacks.put(cb, h);
-            assertFalse(requested.containsKey(cb));
-            requested.put(cb, req);
-            assertFalse(legacyTypeMap.containsKey(cb));
-            if (legacyType != ConnectivityManager.TYPE_NONE) {
-                legacyTypeMap.put(cb, legacyType);
-            }
-        }
-
-        @Override
-        public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb, Handler h) {
-            assertFalse(allCallbacks.containsKey(cb));
-            allCallbacks.put(cb, h);
-            assertFalse(listening.containsKey(cb));
-            listening.put(cb, req);
-        }
-
-        @Override
-        public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb) {
-            fail("Should never be called.");
-        }
-
-        @Override
-        public void registerDefaultNetworkCallback(NetworkCallback cb, Handler h) {
-            fail("Should never be called.");
-        }
-
-        @Override
-        public void registerDefaultNetworkCallback(NetworkCallback cb) {
-            fail("Should never be called.");
-        }
-
-        @Override
-        public void unregisterNetworkCallback(NetworkCallback cb) {
-            if (trackingDefault.contains(cb)) {
-                trackingDefault.remove(cb);
-            } else if (listening.containsKey(cb)) {
-                listening.remove(cb);
-            } else if (requested.containsKey(cb)) {
-                requested.remove(cb);
-                legacyTypeMap.remove(cb);
-            } else {
-                fail("Unexpected callback removed");
-            }
-            allCallbacks.remove(cb);
-
-            assertFalse(allCallbacks.containsKey(cb));
-            assertFalse(trackingDefault.contains(cb));
-            assertFalse(listening.containsKey(cb));
-            assertFalse(requested.containsKey(cb));
-        }
-    }
-
-    public static class TestNetworkAgent {
-        public final TestConnectivityManager cm;
-        public final Network networkId;
-        public final int transportType;
-        public final NetworkCapabilities networkCapabilities;
-        public final LinkProperties linkProperties;
-
-        public TestNetworkAgent(TestConnectivityManager cm, int transportType) {
-            this.cm = cm;
-            this.networkId = new Network(cm.getNetworkId());
-            this.transportType = transportType;
-            networkCapabilities = new NetworkCapabilities();
-            networkCapabilities.addTransportType(transportType);
-            networkCapabilities.addCapability(NET_CAPABILITY_INTERNET);
-            linkProperties = new LinkProperties();
-        }
-
-        public void fakeConnect() {
-            for (NetworkCallback cb : cm.listening.keySet()) {
-                cb.onAvailable(networkId);
-                cb.onCapabilitiesChanged(networkId, copy(networkCapabilities));
-                cb.onLinkPropertiesChanged(networkId, copy(linkProperties));
-            }
-        }
-
-        public void fakeDisconnect() {
-            for (NetworkCallback cb : cm.listening.keySet()) {
-                cb.onLost(networkId);
-            }
-        }
-
-        public void sendLinkProperties() {
-            for (NetworkCallback cb : cm.listening.keySet()) {
-                cb.onLinkPropertiesChanged(networkId, copy(linkProperties));
-            }
-        }
-
-        @Override
-        public String toString() {
-            return String.format("TestNetworkAgent: %s %s", networkId, networkCapabilities);
-        }
-    }
-
     public static class TestStateMachine extends StateMachine {
         public final ArrayList<Message> messages = new ArrayList<>();
         private final State mLoggingState = new LoggingState();
@@ -775,14 +594,6 @@
         }
     }
 
-    static NetworkCapabilities copy(NetworkCapabilities nc) {
-        return new NetworkCapabilities(nc);
-    }
-
-    static LinkProperties copy(LinkProperties lp) {
-        return new LinkProperties(lp);
-    }
-
     static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, String... expected) {
         final Set<String> expectedSet = new HashSet<>();
         Collections.addAll(expectedSet, expected);
@@ -797,4 +608,4 @@
                     expectation, prefixes.contains(new IpPrefix(expectedPrefix)));
         }
     }
-}
+}
\ No newline at end of file