Merge "Import RFC7421_PREFIX_LENGTH from NetworkStackConstants." into main
diff --git a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
index 898b124..4c9460b 100644
--- a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
@@ -17,6 +17,7 @@
 package com.android.networkstack.tethering.apishim.api30;
 
 import android.net.INetd;
+import android.net.IpPrefix;
 import android.net.MacAddress;
 import android.net.TetherStatsParcel;
 import android.os.RemoteException;
@@ -81,14 +82,14 @@
 
     @Override
     public boolean startUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex,
-            @NonNull MacAddress inDstMac, @NonNull MacAddress outSrcMac,
-            @NonNull MacAddress outDstMac, int mtu) {
+            @NonNull IpPrefix sourcePrefix, @NonNull MacAddress inDstMac,
+            @NonNull MacAddress outSrcMac, @NonNull MacAddress outDstMac, int mtu) {
         return true;
     }
 
     @Override
-    public boolean stopUpstreamIpv6Forwarding(int downstreamIfindex,
-            int upstreamIfindex, @NonNull MacAddress inDstMac) {
+    public boolean stopUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex,
+            @NonNull IpPrefix sourcePrefix, @NonNull MacAddress inDstMac) {
         return true;
     }
 
diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
index 3cad1c6..062ecc5 100644
--- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
+++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
@@ -17,7 +17,9 @@
 package com.android.networkstack.tethering.apishim.api31;
 
 import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
+import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
 
+import android.net.IpPrefix;
 import android.net.MacAddress;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -48,6 +50,9 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
 
 /**
  * Bpf coordinator class for API shims.
@@ -196,13 +201,23 @@
         return true;
     }
 
+    @NonNull
+    private TetherUpstream6Key makeUpstream6Key(int downstreamIfindex, @NonNull MacAddress inDstMac,
+            @NonNull IpPrefix sourcePrefix) {
+        byte[] prefixBytes = Arrays.copyOf(sourcePrefix.getRawAddress(), 8);
+        long prefix64 = ByteBuffer.wrap(prefixBytes).order(ByteOrder.BIG_ENDIAN).getLong();
+        return new TetherUpstream6Key(downstreamIfindex, inDstMac, prefix64);
+    }
+
     @Override
     public boolean startUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex,
-            @NonNull MacAddress inDstMac, @NonNull MacAddress outSrcMac,
-            @NonNull MacAddress outDstMac, int mtu) {
+            @NonNull IpPrefix sourcePrefix, @NonNull MacAddress inDstMac,
+            @NonNull MacAddress outSrcMac, @NonNull MacAddress outDstMac, int mtu) {
         if (!isInitialized()) return false;
+        // RFC7421_PREFIX_LENGTH = 64 which is the most commonly used IPv6 subnet prefix length.
+        if (sourcePrefix.getPrefixLength() != RFC7421_PREFIX_LENGTH) return false;
 
-        final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfindex, inDstMac);
+        final TetherUpstream6Key key = makeUpstream6Key(downstreamIfindex, inDstMac, sourcePrefix);
         final Tether6Value value = new Tether6Value(upstreamIfindex, outSrcMac,
                 outDstMac, OsConstants.ETH_P_IPV6, mtu);
         try {
@@ -216,10 +231,12 @@
 
     @Override
     public boolean stopUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex,
-            @NonNull MacAddress inDstMac) {
+            @NonNull IpPrefix sourcePrefix, @NonNull MacAddress inDstMac) {
         if (!isInitialized()) return false;
+        // RFC7421_PREFIX_LENGTH = 64 which is the most commonly used IPv6 subnet prefix length.
+        if (sourcePrefix.getPrefixLength() != RFC7421_PREFIX_LENGTH) return false;
 
-        final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfindex, inDstMac);
+        final TetherUpstream6Key key = makeUpstream6Key(downstreamIfindex, inDstMac, sourcePrefix);
         try {
             mBpfUpstream6Map.deleteEntry(key);
         } catch (ErrnoException e) {
diff --git a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
index 51cecfe..25fa8bc 100644
--- a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
+++ b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
@@ -16,6 +16,7 @@
 
 package com.android.networkstack.tethering.apishim.common;
 
+import android.net.IpPrefix;
 import android.net.MacAddress;
 import android.util.SparseArray;
 
@@ -79,25 +80,27 @@
 
      * @param downstreamIfindex the downstream interface index
      * @param upstreamIfindex the upstream interface index
+     * @param sourcePrefix the source IPv6 prefix
      * @param inDstMac the destination MAC address to use for XDP
      * @param outSrcMac the source MAC address to use for packets
      * @param outDstMac the destination MAC address to use for packets
      * @return true if operation succeeded or was a no-op, false otherwise
      */
     public abstract boolean startUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex,
-            @NonNull MacAddress inDstMac, @NonNull MacAddress outSrcMac,
-            @NonNull MacAddress outDstMac, int mtu);
+            @NonNull IpPrefix sourcePrefix, @NonNull MacAddress inDstMac,
+            @NonNull MacAddress outSrcMac, @NonNull MacAddress outDstMac, int mtu);
 
     /**
      * Stops IPv6 forwarding between the specified interfaces.
 
      * @param downstreamIfindex the downstream interface index
      * @param upstreamIfindex the upstream interface index
+     * @param sourcePrefix the valid source IPv6 prefix
      * @param inDstMac the destination MAC address to use for XDP
      * @return true if operation succeeded or was a no-op, false otherwise
      */
-    public abstract boolean stopUpstreamIpv6Forwarding(int downstreamIfindex,
-            int upstreamIfindex, @NonNull MacAddress inDstMac);
+    public abstract boolean stopUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex,
+            @NonNull IpPrefix sourcePrefix, @NonNull MacAddress inDstMac);
 
     /**
      * Return BPF tethering offload statistics.
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 976f5df..f22ccbd 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -37,6 +37,7 @@
 
 import android.app.usage.NetworkStatsManager;
 import android.net.INetd;
+import android.net.IpPrefix;
 import android.net.LinkProperties;
 import android.net.MacAddress;
 import android.net.NetworkStats;
@@ -119,6 +120,7 @@
     private static final int DUMP_TIMEOUT_MS = 10_000;
     private static final MacAddress NULL_MAC_ADDRESS = MacAddress.fromString(
             "00:00:00:00:00:00");
+    private static final IpPrefix IPV6_ZERO_PREFIX64 = new IpPrefix("::/64");
     private static final String TETHER_DOWNSTREAM4_MAP_PATH = makeMapPath(DOWNSTREAM, 4);
     private static final String TETHER_UPSTREAM4_MAP_PATH = makeMapPath(UPSTREAM, 4);
     private static final String TETHER_DOWNSTREAM6_FS_PATH = makeMapPath(DOWNSTREAM, 6);
@@ -615,8 +617,9 @@
             final int upstream = rule.upstreamIfindex;
             // TODO: support upstream forwarding on non-point-to-point interfaces.
             // TODO: get the MTU from LinkProperties and update the rules when it changes.
-            if (!mBpfCoordinatorShim.startUpstreamIpv6Forwarding(downstream, upstream, rule.srcMac,
-                    NULL_MAC_ADDRESS, NULL_MAC_ADDRESS, NetworkStackConstants.ETHER_MTU)) {
+            if (!mBpfCoordinatorShim.startUpstreamIpv6Forwarding(downstream, upstream,
+                    IPV6_ZERO_PREFIX64, rule.srcMac, NULL_MAC_ADDRESS, NULL_MAC_ADDRESS,
+                    NetworkStackConstants.ETHER_MTU)) {
                 mLog.e("Failed to enable upstream IPv6 forwarding from "
                         + getIfName(downstream) + " to " + getIfName(upstream));
             }
@@ -657,7 +660,7 @@
             final int downstream = rule.downstreamIfindex;
             final int upstream = rule.upstreamIfindex;
             if (!mBpfCoordinatorShim.stopUpstreamIpv6Forwarding(downstream, upstream,
-                    rule.srcMac)) {
+                    IPV6_ZERO_PREFIX64, rule.srcMac)) {
                 mLog.e("Failed to disable upstream IPv6 forwarding from "
                         + getIfName(downstream) + " to " + getIfName(upstream));
             }
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index de15c5b..53c80ae 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -283,13 +283,16 @@
 
     private int initWithHandles(NativeHandle h1, NativeHandle h2) {
         if (h1 == null || h2 == null) {
+            // Set mIOffload to null has two purposes:
+            // 1. NativeHandles can be closed after initWithHandles() fails
+            // 2. Prevent mIOffload.stopOffload() to be called in stopOffload()
+            mIOffload = null;
             mLog.e("Failed to create socket.");
             return OFFLOAD_HAL_VERSION_NONE;
         }
 
         requestSocketDump(h1);
         if (!mIOffload.initOffload(h1, h2, mOffloadHalCallback)) {
-            mIOffload.stopOffload();
             mLog.e("Failed to initialize offload.");
             return OFFLOAD_HAL_VERSION_NONE;
         }
@@ -329,9 +332,9 @@
         mOffloadHalCallback = offloadCb;
         final int version = initWithHandles(h1, h2);
 
-        // Explicitly close FDs for HIDL. AIDL will pass the original FDs to the service,
-        // they shouldn't be closed here.
-        if (version < OFFLOAD_HAL_VERSION_AIDL) {
+        // Explicitly close FDs for HIDL or when mIOffload is null (cleared in initWithHandles).
+        // AIDL will pass the original FDs to the service, they shouldn't be closed here.
+        if (mIOffload == null || mIOffload.getVersion() < OFFLOAD_HAL_VERSION_AIDL) {
             maybeCloseFdInNativeHandles(h1, h2);
         }
         return version;
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherUpstream6Key.java b/Tethering/src/com/android/networkstack/tethering/TetherUpstream6Key.java
index 5893885..36a1c3c 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetherUpstream6Key.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetherUpstream6Key.java
@@ -29,13 +29,17 @@
     @Field(order = 0, type = Type.S32)
     public final int iif; // The input interface index.
 
-    @Field(order = 1, type = Type.EUI48, padding = 2)
+    @Field(order = 1, type = Type.EUI48, padding = 6)
     public final MacAddress dstMac; // Destination ethernet mac address (zeroed iff rawip ingress).
 
-    public TetherUpstream6Key(int iif, @NonNull final MacAddress dstMac) {
+    @Field(order = 2, type = Type.S64)
+    public final long src64; // The top 64-bits of the source ip.
+
+    public TetherUpstream6Key(int iif, @NonNull final MacAddress dstMac, long src64) {
         Objects.requireNonNull(dstMac);
 
         this.iif = iif;
         this.dstMac = dstMac;
+        this.src64 = src64;
     }
 }
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 46e50ef..464778f 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -985,7 +985,7 @@
             throws Exception {
         if (!mBpfDeps.isAtLeastS()) return;
         final TetherUpstream6Key key = new TetherUpstream6Key(TEST_IFACE_PARAMS.index,
-                TEST_IFACE_PARAMS.macAddr);
+                TEST_IFACE_PARAMS.macAddr, 0);
         final Tether6Value value = new Tether6Value(upstreamIfindex,
                 MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS,
                 ETH_P_IPV6, NetworkStackConstants.ETHER_MTU);
@@ -996,7 +996,7 @@
             throws Exception {
         if (!mBpfDeps.isAtLeastS()) return;
         final TetherUpstream6Key key = new TetherUpstream6Key(TEST_IFACE_PARAMS.index,
-                TEST_IFACE_PARAMS.macAddr);
+                TEST_IFACE_PARAMS.macAddr, 0);
         verifyWithOrder(inOrder, mBpfUpstream6Map).deleteEntry(key);
     }
 
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 4f32f3c..8bc4c18 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -641,7 +641,7 @@
     private void verifyStartUpstreamIpv6Forwarding(@Nullable InOrder inOrder, int downstreamIfIndex,
             MacAddress downstreamMac, int upstreamIfindex) throws Exception {
         if (!mDeps.isAtLeastS()) return;
-        final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfIndex, downstreamMac);
+        final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfIndex, downstreamMac, 0);
         final Tether6Value value = new Tether6Value(upstreamIfindex,
                 MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS,
                 ETH_P_IPV6, NetworkStackConstants.ETHER_MTU);
@@ -652,7 +652,7 @@
             MacAddress downstreamMac)
             throws Exception {
         if (!mDeps.isAtLeastS()) return;
-        final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfIndex, downstreamMac);
+        final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfIndex, downstreamMac, 0);
         verifyWithOrder(inOrder, mBpfUpstream6Map).deleteEntry(key);
     }
 
@@ -2192,7 +2192,7 @@
         mBpfDownstream6Map.insertEntry(rule.makeTetherDownstream6Key(), rule.makeTether6Value());
 
         final TetherUpstream6Key upstream6Key = new TetherUpstream6Key(DOWNSTREAM_IFINDEX,
-                DOWNSTREAM_MAC);
+                DOWNSTREAM_MAC, 0);
         final Tether6Value upstream6Value = new Tether6Value(UPSTREAM_IFINDEX,
                 MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS,
                 ETH_P_IPV6, NetworkStackConstants.ETHER_MTU);
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index 8645dd7..c752779 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -194,6 +194,7 @@
 
     TetherUpstream6Key ku = {
             .iif = skb->ifindex,
+            .src64 = 0,
     };
     if (is_ethernet) __builtin_memcpy(downstream ? kd.dstMac : ku.dstMac, eth->h_dest, ETH_ALEN);
 
diff --git a/bpf_progs/offload.h b/bpf_progs/offload.h
index 9dae6c9..1e28f01 100644
--- a/bpf_progs/offload.h
+++ b/bpf_progs/offload.h
@@ -135,10 +135,10 @@
 typedef struct {
     uint32_t iif;              // The input interface index
     uint8_t dstMac[ETH_ALEN];  // destination ethernet mac address (zeroed iff rawip ingress)
-    uint8_t zero[2];           // zero pad for 8 byte alignment
-                               // TODO: extend this to include src ip /64 subnet
+    uint8_t zero[6];           // zero pad for 8 byte alignment
+    uint64_t src64;            // Top 64-bits of the src ip
 } TetherUpstream6Key;
-STRUCT_SIZE(TetherUpstream6Key, 12);
+STRUCT_SIZE(TetherUpstream6Key, 4 + 6 + 6 + 8);  // 24
 
 typedef struct {
     uint32_t iif;              // The input interface index
diff --git a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
index bcedbef..4594f71 100644
--- a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
+++ b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
@@ -66,7 +66,8 @@
                     event.getFoundServiceCount(),
                     event.getFoundCallbackCount(),
                     event.getLostCallbackCount(),
-                    event.getRepliedRequestsCount());
+                    event.getRepliedRequestsCount(),
+                    event.getSentQueryCount());
         }
     }
 
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 53782ac..f7edbe4 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -1920,11 +1920,14 @@
     @Override
     public INsdServiceConnector connect(INsdManagerCallback cb, boolean useJavaBackend) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "NsdService");
+        final int uid = mDeps.getCallingUid();
+        if (cb == null) {
+            throw new IllegalArgumentException("Unknown client callback from uid=" + uid);
+        }
         if (DBG) Log.d(TAG, "New client connect. useJavaBackend=" + useJavaBackend);
         final INsdServiceConnector connector = new NsdServiceConnector();
         mNsdStateMachine.sendMessage(mNsdStateMachine.obtainMessage(NsdManager.REGISTER_CLIENT,
-                new ConnectorArgs((NsdServiceConnector) connector, cb, useJavaBackend,
-                        mDeps.getCallingUid())));
+                new ConnectorArgs((NsdServiceConnector) connector, cb, useJavaBackend, uid)));
         return connector;
     }
 
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 3f4113a..c46eada 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -63,6 +63,7 @@
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
 import static com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport;
 import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT;
 
@@ -3248,7 +3249,8 @@
      * Default external settings that read from
      * {@link android.provider.Settings.Global}.
      */
-    private static class DefaultNetworkStatsSettings implements NetworkStatsSettings {
+    @VisibleForTesting(visibility = PRIVATE)
+    static class DefaultNetworkStatsSettings implements NetworkStatsSettings {
         DefaultNetworkStatsSettings() {}
 
         @Override
diff --git a/service/src/com/android/metrics/stats.proto b/service/src/com/android/metrics/stats.proto
index 006d20a..99afb90 100644
--- a/service/src/com/android/metrics/stats.proto
+++ b/service/src/com/android/metrics/stats.proto
@@ -61,6 +61,9 @@
 
   // Record query service count before unregistered service
   optional int32 replied_requests_count = 11;
+
+  // Record sent query count before stopped discovery
+  optional int32 sent_query_count = 12;
 }
 
 /**
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 7731ac9..e4ee8de 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -910,12 +910,16 @@
         val record = NsdRegistrationRecord()
         nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, record)
         val addOrUpdateEvent = offloadEngine
-            .expectCallback<TestNsdOffloadEngine.OffloadEvent.AddOrUpdateEvent>()
+            .expectCallbackEventually<TestNsdOffloadEngine.OffloadEvent.AddOrUpdateEvent> {
+                it.info.key.serviceName == serviceName
+            }
         checkOffloadServiceInfo(addOrUpdateEvent.info)
 
         nsdManager.unregisterService(record)
         val unregisterEvent = offloadEngine
-            .expectCallback<TestNsdOffloadEngine.OffloadEvent.RemoveEvent>()
+            .expectCallbackEventually<TestNsdOffloadEngine.OffloadEvent.RemoveEvent> {
+                it.info.key.serviceName == serviceName
+            }
         checkOffloadServiceInfo(unregisterEvent.info)
 
         runAsShell(NETWORK_SETTINGS) {
diff --git a/tests/mts/bpf_existence_test.cpp b/tests/mts/bpf_existence_test.cpp
index c294e7b..442d69f 100644
--- a/tests/mts/bpf_existence_test.cpp
+++ b/tests/mts/bpf_existence_test.cpp
@@ -33,6 +33,8 @@
 using android::modules::sdklevel::IsAtLeastR;
 using android::modules::sdklevel::IsAtLeastS;
 using android::modules::sdklevel::IsAtLeastT;
+using android::modules::sdklevel::IsAtLeastU;
+using android::modules::sdklevel::IsAtLeastV;
 
 #define PLATFORM "/sys/fs/bpf/"
 #define TETHERING "/sys/fs/bpf/tethering/"
@@ -147,10 +149,14 @@
     // so we should only test for the removal of stuff that was mainline'd,
     // and for the presence of mainline stuff.
 
+    // Note: Q is no longer supported by mainline
+    ASSERT_TRUE(IsAtLeastR());
+
     // R can potentially run on pre-4.9 kernel non-eBPF capable devices.
     DO_EXPECT(IsAtLeastR() && !IsAtLeastS() && isAtLeastKernelVersion(4, 9, 0), PLATFORM_ONLY_IN_R);
 
     // S requires Linux Kernel 4.9+ and thus requires eBPF support.
+    if (IsAtLeastS()) ASSERT_TRUE(isAtLeastKernelVersion(4, 9, 0));
     DO_EXPECT(IsAtLeastS(), MAINLINE_FOR_S_PLUS);
     DO_EXPECT(IsAtLeastS() && isAtLeastKernelVersion(5, 10, 0), MAINLINE_FOR_S_5_10_PLUS);
 
@@ -163,6 +169,10 @@
     DO_EXPECT(IsAtLeastT() && isAtLeastKernelVersion(5, 15, 0), MAINLINE_FOR_T_5_15_PLUS);
 
     // U requires Linux Kernel 4.14+, but nothing (as yet) added or removed in U.
+    if (IsAtLeastU()) ASSERT_TRUE(isAtLeastKernelVersion(4, 14, 0));
+
+    // V requires Linux Kernel 4.19+, but nothing (as yet) added or removed in V.
+    if (IsAtLeastV()) ASSERT_TRUE(isAtLeastKernelVersion(4, 19, 0));
 
     for (const auto& file : mustExist) {
         EXPECT_EQ(0, access(file.c_str(), R_OK)) << file << " does not exist";
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 55384b3..f778075 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -1585,6 +1585,20 @@
         lockOrder.verify(mMulticastLock).release();
     }
 
+    @Test
+    public void testNullINsdManagerCallback() {
+        final NsdService service = new NsdService(mContext, mHandler, CLEANUP_DELAY_MS, mDeps) {
+            @Override
+            public INsdServiceConnector connect(INsdManagerCallback baseCb,
+                    boolean runNewMdnsBackend) {
+                // Pass null INsdManagerCallback
+                return super.connect(null /* cb */, runNewMdnsBackend);
+            }
+        };
+
+        assertThrows(IllegalArgumentException.class, () -> new NsdManager(mContext, service));
+    }
+
     private void waitForIdle() {
         HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
     }
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 6292d45..9453617 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -79,7 +79,6 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doReturn;
@@ -240,7 +239,9 @@
     private @Mock INetd mNetd;
     private @Mock TetheringManager mTetheringManager;
     private @Mock NetworkStatsFactory mStatsFactory;
-    private @Mock NetworkStatsSettings mSettings;
+    @NonNull
+    private final TestNetworkStatsSettings mSettings =
+            new TestNetworkStatsSettings(HOUR_IN_MILLIS, WEEK_IN_MILLIS);
     private @Mock IBinder mUsageCallbackBinder;
     private TestableUsageCallback mUsageCallback;
     private @Mock AlarmManager mAlarmManager;
@@ -533,7 +534,6 @@
         mStatsDir = null;
 
         mNetd = null;
-        mSettings = null;
 
         mSession.close();
         mService = null;
@@ -1765,7 +1765,7 @@
     }
 
     private void setCombineSubtypeEnabled(boolean enable) {
-        doReturn(enable).when(mSettings).getCombineSubtypeEnabled();
+        mSettings.setCombineSubtypeEnabled(enable);
         mHandler.post(() -> mContentObserver.onChange(false, Settings.Global
                     .getUriFor(Settings.Global.NETSTATS_COMBINE_SUBTYPE_ENABLED)));
         waitForIdle();
@@ -2289,21 +2289,80 @@
         mockSettings(HOUR_IN_MILLIS, WEEK_IN_MILLIS);
     }
 
-    private void mockSettings(long bucketDuration, long deleteAge) throws Exception {
-        doReturn(HOUR_IN_MILLIS).when(mSettings).getPollInterval();
-        doReturn(0L).when(mSettings).getPollDelay();
-        doReturn(true).when(mSettings).getSampleEnabled();
-        doReturn(false).when(mSettings).getCombineSubtypeEnabled();
+    private void mockSettings(long bucketDuration, long deleteAge) {
+        mSettings.setConfig(new Config(bucketDuration, deleteAge, deleteAge));
+    }
 
-        final Config config = new Config(bucketDuration, deleteAge, deleteAge);
-        doReturn(config).when(mSettings).getXtConfig();
-        doReturn(config).when(mSettings).getUidConfig();
-        doReturn(config).when(mSettings).getUidTagConfig();
+    // Note that this object will be accessed from test main thread and service handler thread.
+    // Thus, it has to be thread safe in order to prevent from flakiness.
+    private static class TestNetworkStatsSettings
+            extends NetworkStatsService.DefaultNetworkStatsSettings {
 
-        doReturn(MB_IN_BYTES).when(mSettings).getGlobalAlertBytes(anyLong());
-        doReturn(MB_IN_BYTES).when(mSettings).getXtPersistBytes(anyLong());
-        doReturn(MB_IN_BYTES).when(mSettings).getUidPersistBytes(anyLong());
-        doReturn(MB_IN_BYTES).when(mSettings).getUidTagPersistBytes(anyLong());
+        @NonNull
+        private volatile Config mConfig;
+        private final AtomicBoolean mCombineSubtypeEnabled = new AtomicBoolean();
+
+        TestNetworkStatsSettings(long bucketDuration, long deleteAge) {
+            mConfig = new Config(bucketDuration, deleteAge, deleteAge);
+        }
+
+        void setConfig(@NonNull Config config) {
+            mConfig = config;
+        }
+
+        @Override
+        public long getPollDelay() {
+            return 0L;
+        }
+
+        @Override
+        public long getGlobalAlertBytes(long def) {
+            return MB_IN_BYTES;
+        }
+
+        @Override
+        public Config getXtConfig() {
+            return mConfig;
+        }
+
+        @Override
+        public Config getUidConfig() {
+            return mConfig;
+        }
+
+        @Override
+        public Config getUidTagConfig() {
+            return mConfig;
+        }
+
+        @Override
+        public long getXtPersistBytes(long def) {
+            return MB_IN_BYTES;
+        }
+
+        @Override
+        public long getUidPersistBytes(long def) {
+            return MB_IN_BYTES;
+        }
+
+        @Override
+        public long getUidTagPersistBytes(long def) {
+            return MB_IN_BYTES;
+        }
+
+        @Override
+        public boolean getCombineSubtypeEnabled() {
+            return mCombineSubtypeEnabled.get();
+        }
+
+        public void setCombineSubtypeEnabled(boolean enable) {
+            mCombineSubtypeEnabled.set(enable);
+        }
+
+        @Override
+        public boolean getAugmentEnabled() {
+            return false;
+        }
     }
 
     private void assertStatsFilesExist(boolean exist) {