Merge "Remove the VisibleForTesting annotation on IaPrefixOption constructor." into main
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index d3e68b7..d8d0acc 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -34,6 +34,7 @@
 java_library {
   name: "net-utils-device-common",
   srcs: [
+      "device/com/android/net/module/util/arp/ArpPacket.java",
       "device/com/android/net/module/util/DeviceConfigUtils.java",
       "device/com/android/net/module/util/DomainUtils.java",
       "device/com/android/net/module/util/FdEventsReader.java",
@@ -245,6 +246,7 @@
         "framework-annotations-lib",
         "framework-connectivity.stubs.module_lib",
         "framework-connectivity-t.stubs.module_lib",
+        "framework-location.stubs.module_lib",
     ],
     jarjar_rules: "jarjar-rules-shared.txt",
     visibility: [
diff --git a/staticlibs/device/com/android/net/module/util/BpfMap.java b/staticlibs/device/com/android/net/module/util/BpfMap.java
index 9df2b03..d45cace 100644
--- a/staticlibs/device/com/android/net/module/util/BpfMap.java
+++ b/staticlibs/device/com/android/net/module/util/BpfMap.java
@@ -194,9 +194,11 @@
     }
 
     private K getNextKeyInternal(@Nullable K key) throws ErrnoException {
-        final byte[] rawKey = getNextRawKey(
-                key == null ? null : key.writeToBytes());
-        if (rawKey == null) return null;
+        byte[] rawKey = new byte[mKeySize];
+
+        if (!nativeGetNextMapKey(mMapFd.getFd(),
+                                 key == null ? null : key.writeToBytes(),
+                                 rawKey)) return null;
 
         final ByteBuffer buffer = ByteBuffer.wrap(rawKey);
         buffer.order(ByteOrder.nativeOrder());
@@ -215,13 +217,6 @@
         return getNextKeyInternal(key);
     }
 
-    private byte[] getNextRawKey(@Nullable final byte[] key) throws ErrnoException {
-        byte[] nextKey = new byte[mKeySize];
-        if (nativeGetNextMapKey(mMapFd.getFd(), key, nextKey)) return nextKey;
-
-        return null;
-    }
-
     /** Get the first key of eBpf map. */
     @Override
     public K getFirstKey() throws ErrnoException {
@@ -233,30 +228,23 @@
     public boolean containsKey(@NonNull K key) throws ErrnoException {
         Objects.requireNonNull(key);
 
-        final byte[] rawValue = getRawValue(key.writeToBytes());
-        return rawValue != null;
+        byte[] rawValue = new byte[mValueSize];
+        return nativeFindMapEntry(mMapFd.getFd(), key.writeToBytes(), rawValue);
     }
 
     /** Retrieve a value from the map. Return null if there is no such key. */
     @Override
     public V getValue(@NonNull K key) throws ErrnoException {
         Objects.requireNonNull(key);
-        final byte[] rawValue = getRawValue(key.writeToBytes());
 
-        if (rawValue == null) return null;
+        byte[] rawValue = new byte[mValueSize];
+        if (!nativeFindMapEntry(mMapFd.getFd(), key.writeToBytes(), rawValue)) return null;
 
         final ByteBuffer buffer = ByteBuffer.wrap(rawValue);
         buffer.order(ByteOrder.nativeOrder());
         return Struct.parse(mValueClass, buffer);
     }
 
-    private byte[] getRawValue(final byte[] key) throws ErrnoException {
-        byte[] value = new byte[mValueSize];
-        if (nativeFindMapEntry(mMapFd.getFd(), key, value)) return value;
-
-        return null;
-    }
-
     /**
      * Iterate through the map and handle each key -> value retrieved base on the given BiConsumer.
      * The given BiConsumer may to delete the passed-in entry, but is not allowed to perform any
diff --git a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
index bea227d..fb130f6 100644
--- a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
+++ b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
@@ -17,6 +17,7 @@
 package com.android.net.module.util;
 
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
 
 import static com.android.net.module.util.FeatureVersions.CONNECTIVITY_MODULE_ID;
@@ -207,8 +208,9 @@
      *                       null.
      * @return true if this feature is enabled, or false if disabled.
      */
-    public static boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
-            @NonNull String name, @NonNull String moduleName, boolean defaultEnabled) {
+    public static boolean isTetheringFeatureEnabled(@NonNull Context context,
+            @NonNull String namespace, @NonNull String name, @NonNull String moduleName,
+            boolean defaultEnabled) {
         // TODO: migrate callers to a non-generic isTetheringFeatureEnabled method.
         if (!TETHERING_MODULE_NAME.equals(moduleName)) {
             throw new IllegalArgumentException(
@@ -334,16 +336,42 @@
     }
 
     /**
-     * Check whether one specific experimental feature in tethering module from {@link DeviceConfig}
-     * is disabled by setting a non-zero value in the property.
+     * Check whether one specific experimental feature in specific namespace from
+     * {@link DeviceConfig} is not disabled. Feature can be disabled by setting a non-zero
+     * value in the property. If the feature is enabled by default and disabled by flag push
+     * (kill switch), this method should be used. If the feature is disabled by default and
+     * enabled by flag push, {@link #isFeatureEnabled} should be used.
      *
+     * @param namespace The namespace containing the property to look up.
      * @param name The name of the property to look up.
-     * @return true if this feature is force disabled, or false if not disabled.
+     * @return true if this feature is enabled, or false if disabled.
      */
-    public static boolean isTetheringFeatureForceDisabled(String name) {
-        final int propertyVersion = getDeviceConfigPropertyInt(NAMESPACE_TETHERING, name,
+    private static boolean isFeatureNotChickenedOut(String namespace, String name) {
+        final int propertyVersion = getDeviceConfigPropertyInt(namespace, name,
                 0 /* default value */);
-        return propertyVersion != 0;
+        return propertyVersion == 0;
+    }
+
+    /**
+     * Check whether one specific experimental feature in Tethering module from {@link DeviceConfig}
+     * is not disabled.
+     *
+     * @param name The name of the property in tethering module to look up.
+     * @return true if this feature is enabled, or false if disabled.
+     */
+    public static boolean isTetheringFeatureNotChickenedOut(String name) {
+        return isFeatureNotChickenedOut(NAMESPACE_TETHERING, name);
+    }
+
+    /**
+     * Check whether one specific experimental feature in NetworkStack module from
+     * {@link DeviceConfig} is not disabled.
+     *
+     * @param name The name of the property in NetworkStack module to look up.
+     * @return true if this feature is enabled, or false if disabled.
+     */
+    public static boolean isNetworkStackFeatureNotChickenedOut(String name) {
+        return isFeatureNotChickenedOut(NAMESPACE_CONNECTIVITY, name);
     }
 
     /**
diff --git a/staticlibs/device/com/android/net/module/util/FeatureVersions.java b/staticlibs/device/com/android/net/module/util/FeatureVersions.java
index 4986a58..149756c 100644
--- a/staticlibs/device/com/android/net/module/util/FeatureVersions.java
+++ b/staticlibs/device/com/android/net/module/util/FeatureVersions.java
@@ -22,13 +22,24 @@
  * @hide
  */
 public class FeatureVersions {
-    public static final long MODULE_MASK = 0xFF00_000000000L;
-    public static final long VERSION_MASK = 0x0000_FFFFFFFFFL;
-    public static final long CONNECTIVITY_MODULE_ID = 0x0100_000000000L;
-    public static final long NETWORK_STACK_MODULE_ID = 0x0200_000000000L;
+    /**
+     * This constant is used to do bitwise shift operation to create module ids.
+     * The module version is composed with 9 digits which is placed in the lower 36 bits.
+     */
+    private static final int MODULE_SHIFT = 36;
+    /**
+     * The bitmask to do bitwise-and(i.e. {@code &}) operation to get the module id.
+     */
+    public static final long MODULE_MASK = 0xFF0_0000_0000L;
+    /**
+     * The bitmask to do bitwise-and(i.e. {@code &}) operation to get the module version.
+     */
+    public static final long VERSION_MASK = 0x00F_FFFF_FFFFL;
+    public static final long CONNECTIVITY_MODULE_ID = 0x01L << MODULE_SHIFT;
+    public static final long NETWORK_STACK_MODULE_ID = 0x02L << MODULE_SHIFT;
     // CLAT_ADDRESS_TRANSLATE is a feature of the network stack, which doesn't throw when system
     // try to add a NAT-T keepalive packet filter with v6 address, introduced in version
     // M-2023-Sept on July 3rd, 2023.
     public static final long FEATURE_CLAT_ADDRESS_TRANSLATE =
-            NETWORK_STACK_MODULE_ID + 340900000L;
+            NETWORK_STACK_MODULE_ID + 34_09_00_000L;
 }
diff --git a/staticlibs/device/com/android/net/module/util/arp/ArpPacket.java b/staticlibs/device/com/android/net/module/util/arp/ArpPacket.java
new file mode 100644
index 0000000..dab9694
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/arp/ArpPacket.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.arp;
+
+import static android.system.OsConstants.ETH_P_ARP;
+import static android.system.OsConstants.ETH_P_IP;
+
+import static com.android.net.module.util.NetworkStackConstants.ARP_ETHER_IPV4_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ARP_HWTYPE_ETHER;
+import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
+import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_LEN;
+
+import android.net.MacAddress;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * Defines basic data and operations needed to build and parse packets for the
+ * ARP protocol.
+ *
+ * @hide
+ */
+public class ArpPacket {
+    private static final String TAG = "ArpPacket";
+
+    public final short opCode;
+    public final Inet4Address senderIp;
+    public final Inet4Address targetIp;
+    public final MacAddress senderHwAddress;
+    public final MacAddress targetHwAddress;
+
+    ArpPacket(short opCode, MacAddress senderHwAddress, Inet4Address senderIp,
+            MacAddress targetHwAddress, Inet4Address targetIp) {
+        this.opCode = opCode;
+        this.senderHwAddress = senderHwAddress;
+        this.senderIp = senderIp;
+        this.targetHwAddress = targetHwAddress;
+        this.targetIp = targetIp;
+    }
+
+    /**
+     * Build an ARP packet from the required specified parameters.
+     */
+    @VisibleForTesting
+    public static ByteBuffer buildArpPacket(final byte[] dstMac, final byte[] srcMac,
+            final byte[] targetIp, final byte[] targetHwAddress, byte[] senderIp,
+            final short opCode) {
+        final ByteBuffer buf = ByteBuffer.allocate(ARP_ETHER_IPV4_LEN);
+
+        // Ether header
+        buf.put(dstMac);
+        buf.put(srcMac);
+        buf.putShort((short) ETH_P_ARP);
+
+        // ARP header
+        buf.putShort((short) ARP_HWTYPE_ETHER);  // hrd
+        buf.putShort((short) ETH_P_IP);          // pro
+        buf.put((byte) ETHER_ADDR_LEN);          // hln
+        buf.put((byte) IPV4_ADDR_LEN);           // pln
+        buf.putShort(opCode);                    // op
+        buf.put(srcMac);                         // sha
+        buf.put(senderIp);                       // spa
+        buf.put(targetHwAddress);                // tha
+        buf.put(targetIp);                       // tpa
+        buf.flip();
+        return buf;
+    }
+
+    /**
+     * Parse an ARP packet from a ByteBuffer object.
+     */
+    @VisibleForTesting
+    public static ArpPacket parseArpPacket(final byte[] recvbuf, final int length)
+            throws ParseException {
+        try {
+            if (length < ARP_ETHER_IPV4_LEN || recvbuf.length < length) {
+                throw new ParseException("Invalid packet length: " + length);
+            }
+
+            final ByteBuffer buffer = ByteBuffer.wrap(recvbuf, 0, length);
+            byte[] l2dst = new byte[ETHER_ADDR_LEN];
+            byte[] l2src = new byte[ETHER_ADDR_LEN];
+            buffer.get(l2dst);
+            buffer.get(l2src);
+
+            final short etherType = buffer.getShort();
+            if (etherType != ETH_P_ARP) {
+                throw new ParseException("Incorrect Ether Type: " + etherType);
+            }
+
+            final short hwType = buffer.getShort();
+            if (hwType != ARP_HWTYPE_ETHER) {
+                throw new ParseException("Incorrect HW Type: " + hwType);
+            }
+
+            final short protoType = buffer.getShort();
+            if (protoType != ETH_P_IP) {
+                throw new ParseException("Incorrect Protocol Type: " + protoType);
+            }
+
+            final byte hwAddrLength = buffer.get();
+            if (hwAddrLength != ETHER_ADDR_LEN) {
+                throw new ParseException("Incorrect HW address length: " + hwAddrLength);
+            }
+
+            final byte ipAddrLength = buffer.get();
+            if (ipAddrLength != IPV4_ADDR_LEN) {
+                throw new ParseException("Incorrect Protocol address length: " + ipAddrLength);
+            }
+
+            final short opCode = buffer.getShort();
+            if (opCode != ARP_REQUEST && opCode != ARP_REPLY) {
+                throw new ParseException("Incorrect opCode: " + opCode);
+            }
+
+            byte[] senderHwAddress = new byte[ETHER_ADDR_LEN];
+            byte[] senderIp = new byte[IPV4_ADDR_LEN];
+            buffer.get(senderHwAddress);
+            buffer.get(senderIp);
+
+            byte[] targetHwAddress = new byte[ETHER_ADDR_LEN];
+            byte[] targetIp = new byte[IPV4_ADDR_LEN];
+            buffer.get(targetHwAddress);
+            buffer.get(targetIp);
+
+            return new ArpPacket(opCode, MacAddress.fromBytes(senderHwAddress),
+                    (Inet4Address) InetAddress.getByAddress(senderIp),
+                    MacAddress.fromBytes(targetHwAddress),
+                    (Inet4Address) InetAddress.getByAddress(targetIp));
+        } catch (IndexOutOfBoundsException e) {
+            throw new ParseException("Invalid index when wrapping a byte array into a buffer");
+        } catch (BufferUnderflowException e) {
+            throw new ParseException("Invalid buffer position");
+        } catch (IllegalArgumentException e) {
+            throw new ParseException("Invalid MAC address representation");
+        } catch (UnknownHostException e) {
+            throw new ParseException("Invalid IP address of Host");
+        }
+    }
+
+    /**
+     * Thrown when parsing ARP packet failed.
+     */
+    public static class ParseException extends Exception {
+        ParseException(String message) {
+            super(message);
+        }
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/PermissionUtils.java b/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
index 8315b8f..d5b4c90 100644
--- a/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
@@ -21,12 +21,15 @@
 import static android.Manifest.permission.NETWORK_STACK;
 import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.pm.PermissionInfo.PROTECTION_SIGNATURE;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
 import android.os.Binder;
 
 import java.io.PrintWriter;
@@ -54,6 +57,25 @@
     }
 
     /**
+     * Return true if the permission has system signature.
+     */
+    public static boolean isSystemSignaturePermission(@NonNull Context context,
+            @NonNull String permission) {
+        try {
+            PermissionInfo permissionInfo = context.getPackageManager().getPermissionInfo(
+                    permission, 0 /* flags */);
+            if (permissionInfo == null) {
+                return false;
+            }
+            return "android".equals(permissionInfo.packageName)
+                    && permissionInfo.getProtection() == PROTECTION_SIGNATURE;
+        } catch (PackageManager.NameNotFoundException ignored) {
+            // Ignored the NameNotFoundException and return false
+        }
+        return false;
+    }
+
+    /**
      * Return true if the context has one of give permission that is allowed
      * for a particular process and user ID running in the system.
      */
diff --git a/staticlibs/lint-baseline.xml b/staticlibs/lint-baseline.xml
new file mode 100644
index 0000000..d413b2a
--- /dev/null
+++ b/staticlibs/lint-baseline.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.0.0-dev" type="baseline" dependencies="true" variant="all" version="8.0.0-dev">
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 30 (current min is 29): `android.net.LinkProperties#getAddresses`"
+        errorLine1="        final Collection&lt;InetAddress&gt; leftAddresses = left.getAddresses();"
+        errorLine2="                                                           ~~~~~~~~~~~~">
+        <location
+            file="frameworks/libs/net/common/framework/com/android/net/module/util/LinkPropertiesUtils.java"
+            line="158"
+            column="60"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 30 (current min is 29): `android.net.LinkProperties#getAddresses`"
+        errorLine1="        final Collection&lt;InetAddress&gt; rightAddresses = right.getAddresses();"
+        errorLine2="                                                             ~~~~~~~~~~~~">
+        <location
+            file="frameworks/libs/net/common/framework/com/android/net/module/util/LinkPropertiesUtils.java"
+            line="159"
+            column="62"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 30 (current min is 29): `android.net.NetworkStats#addEntry`"
+        errorLine1="            stats = stats.addEntry(entry);"
+        errorLine2="                          ~~~~~~~~">
+        <location
+            file="frameworks/libs/net/common/framework/com/android/net/module/util/NetworkStatsUtils.java"
+            line="113"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 30 (current min is 29): `new android.net.NetworkStats.Entry`"
+        errorLine1="        return new android.net.NetworkStats.Entry("
+        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/libs/net/common/framework/com/android/net/module/util/NetworkStatsUtils.java"
+            line="120"
+            column="16"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 30 (current min is 29): `new android.net.NetworkStats`"
+        errorLine1="        android.net.NetworkStats stats = new android.net.NetworkStats(0L, 0);"
+        errorLine2="                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/libs/net/common/framework/com/android/net/module/util/NetworkStatsUtils.java"
+            line="108"
+            column="42"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Call requires API level 31 (current min is 29): `new android.system.NetlinkSocketAddress`"
+        errorLine1="        return new NetlinkSocketAddress(portId, groupsMask);"
+        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/libs/net/common/device/com/android/net/module/util/SocketUtils.java"
+            line="44"
+            column="16"/>
+    </issue>
+
+</issues>
\ No newline at end of file
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfMap.h b/staticlibs/native/bpf_headers/include/bpf/BpfMap.h
index 51e6d16..847083e 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfMap.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfMap.h
@@ -29,6 +29,10 @@
 namespace android {
 namespace bpf {
 
+using base::Result;
+using base::unique_fd;
+using std::function;
+
 // This is a class wrapper for eBPF maps. The eBPF map is a special in-kernel
 // data structure that stores data in <Key, Value> pairs. It can be read/write
 // from userspace by passing syscalls with the map file descriptor. This class
@@ -80,7 +84,7 @@
     }
 #endif
 
-    base::Result<Key> getFirstKey() const {
+    Result<Key> getFirstKey() const {
         Key firstKey;
         if (getFirstMapKey(mMapFd, &firstKey)) {
             return ErrnoErrorf("Get firstKey map {} failed", mMapFd.get());
@@ -88,7 +92,7 @@
         return firstKey;
     }
 
-    base::Result<Key> getNextKey(const Key& key) const {
+    Result<Key> getNextKey(const Key& key) const {
         Key nextKey;
         if (getNextMapKey(mMapFd, &key, &nextKey)) {
             return ErrnoErrorf("Get next key of map {} failed", mMapFd.get());
@@ -96,14 +100,14 @@
         return nextKey;
     }
 
-    base::Result<void> writeValue(const Key& key, const Value& value, uint64_t flags) {
+    Result<void> writeValue(const Key& key, const Value& value, uint64_t flags) {
         if (writeToMapEntry(mMapFd, &key, &value, flags)) {
             return ErrnoErrorf("Write to map {} failed", mMapFd.get());
         }
         return {};
     }
 
-    base::Result<Value> readValue(const Key key) const {
+    Result<Value> readValue(const Key key) const {
         Value value;
         if (findMapEntry(mMapFd, &key, &value)) {
             return ErrnoErrorf("Read value of map {} failed", mMapFd.get());
@@ -111,7 +115,7 @@
         return value;
     }
 
-    base::Result<void> deleteValue(const Key& key) {
+    Result<void> deleteValue(const Key& key) {
         if (deleteMapEntry(mMapFd, &key)) {
             return ErrnoErrorf("Delete entry from map {} failed", mMapFd.get());
         }
@@ -119,7 +123,7 @@
     }
 
   protected:
-    [[clang::reinitializes]] base::Result<void> init(const char* path, int fd) {
+    [[clang::reinitializes]] Result<void> init(const char* path, int fd) {
         mMapFd.reset(fd);
         if (!mMapFd.ok()) {
             return ErrnoErrorf("Pinned map not accessible or does not exist: ({})", path);
@@ -134,7 +138,7 @@
 
   public:
     // Function that tries to get map from a pinned path.
-    [[clang::reinitializes]] base::Result<void> init(const char* path) {
+    [[clang::reinitializes]] Result<void> init(const char* path) {
         return init(path, mapRetrieveRW(path));
     }
 
@@ -144,7 +148,7 @@
     // this should only ever be used by test code, it is equivalent to:
     //   .reset(createMap(type, keysize, valuesize, max_entries, map_flags)
     // TODO: derive map_flags from BpfMap vs BpfMapRO
-    [[clang::reinitializes]] base::Result<void> resetMap(bpf_map_type map_type,
+    [[clang::reinitializes]] Result<void> resetMap(bpf_map_type map_type,
                                                          uint32_t max_entries,
                                                          uint32_t map_flags = 0) {
         mMapFd.reset(createMap(map_type, sizeof(Key), sizeof(Value), max_entries, map_flags));
@@ -155,30 +159,30 @@
 
     // Iterate through the map and handle each key retrieved based on the filter
     // without modification of map content.
-    base::Result<void> iterate(
-            const std::function<base::Result<void>(const Key& key, const BpfMap<Key, Value>& map)>&
-                    filter) const;
+    Result<void> iterate(
+            const function<Result<void>(const Key& key,
+                                        const BpfMap<Key, Value>& map)>& filter) const;
 
     // Iterate through the map and get each <key, value> pair, handle each <key,
     // value> pair based on the filter without modification of map content.
-    base::Result<void> iterateWithValue(
-            const std::function<base::Result<void>(const Key& key, const Value& value,
-                                                   const BpfMap<Key, Value>& map)>& filter) const;
+    Result<void> iterateWithValue(
+            const function<Result<void>(const Key& key, const Value& value,
+                                        const BpfMap<Key, Value>& map)>& filter) const;
 
     // Iterate through the map and handle each key retrieved based on the filter
-    base::Result<void> iterate(
-            const std::function<base::Result<void>(const Key& key, BpfMap<Key, Value>& map)>&
-                    filter);
+    Result<void> iterate(
+            const function<Result<void>(const Key& key,
+                                        BpfMap<Key, Value>& map)>& filter);
 
     // Iterate through the map and get each <key, value> pair, handle each <key,
     // value> pair based on the filter.
-    base::Result<void> iterateWithValue(
-            const std::function<base::Result<void>(const Key& key, const Value& value,
-                                                   BpfMap<Key, Value>& map)>& filter);
-
-    const base::unique_fd& getMap() const { return mMapFd; };
+    Result<void> iterateWithValue(
+            const function<Result<void>(const Key& key, const Value& value,
+                                        BpfMap<Key, Value>& map)>& filter);
 
 #ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING
+    const unique_fd& getMap() const { return mMapFd; };
+
     // Copy assignment operator - due to need for fd duping, should not be used in non-test code.
     BpfMap<Key, Value>& operator=(const BpfMap<Key, Value>& other) {
         if (this != &other) mMapFd.reset(fcntl(other.mMapFd.get(), F_DUPFD_CLOEXEC, 0));
@@ -197,7 +201,7 @@
         return *this;
     }
 
-    void reset(base::unique_fd fd) = delete;
+    void reset(unique_fd fd) = delete;
 
 #ifdef BPF_MAP_MAKE_VISIBLE_FOR_TESTING
     // Note that unique_fd.reset() carefully saves and restores the errno,
@@ -216,7 +220,7 @@
 
     bool isValid() const { return mMapFd.ok(); }
 
-    base::Result<void> clear() {
+    Result<void> clear() {
         while (true) {
             auto key = getFirstKey();
             if (!key.ok()) {
@@ -233,28 +237,25 @@
         }
     }
 
-    base::Result<bool> isEmpty() const {
+    Result<bool> isEmpty() const {
         auto key = getFirstKey();
-        if (!key.ok()) {
-            // Return error code ENOENT means the map is empty
-            if (key.error().code() == ENOENT) return true;
-            return key.error();
-        }
-        return false;
+        if (key.ok()) return false;
+        if (key.error().code() == ENOENT) return true;
+        return key.error();
     }
 
   private:
-    base::unique_fd mMapFd;
+    unique_fd mMapFd;
 };
 
 template <class Key, class Value>
-base::Result<void> BpfMap<Key, Value>::iterate(
-        const std::function<base::Result<void>(const Key& key, const BpfMap<Key, Value>& map)>&
-                filter) const {
-    base::Result<Key> curKey = getFirstKey();
+Result<void> BpfMap<Key, Value>::iterate(
+        const function<Result<void>(const Key& key,
+                                    const BpfMap<Key, Value>& map)>& filter) const {
+    Result<Key> curKey = getFirstKey();
     while (curKey.ok()) {
-        const base::Result<Key>& nextKey = getNextKey(curKey.value());
-        base::Result<void> status = filter(curKey.value(), *this);
+        const Result<Key>& nextKey = getNextKey(curKey.value());
+        Result<void> status = filter(curKey.value(), *this);
         if (!status.ok()) return status;
         curKey = nextKey;
     }
@@ -263,15 +264,15 @@
 }
 
 template <class Key, class Value>
-base::Result<void> BpfMap<Key, Value>::iterateWithValue(
-        const std::function<base::Result<void>(const Key& key, const Value& value,
-                                               const BpfMap<Key, Value>& map)>& filter) const {
-    base::Result<Key> curKey = getFirstKey();
+Result<void> BpfMap<Key, Value>::iterateWithValue(
+        const function<Result<void>(const Key& key, const Value& value,
+                                    const BpfMap<Key, Value>& map)>& filter) const {
+    Result<Key> curKey = getFirstKey();
     while (curKey.ok()) {
-        const base::Result<Key>& nextKey = getNextKey(curKey.value());
-        base::Result<Value> curValue = readValue(curKey.value());
+        const Result<Key>& nextKey = getNextKey(curKey.value());
+        Result<Value> curValue = readValue(curKey.value());
         if (!curValue.ok()) return curValue.error();
-        base::Result<void> status = filter(curKey.value(), curValue.value(), *this);
+        Result<void> status = filter(curKey.value(), curValue.value(), *this);
         if (!status.ok()) return status;
         curKey = nextKey;
     }
@@ -280,12 +281,13 @@
 }
 
 template <class Key, class Value>
-base::Result<void> BpfMap<Key, Value>::iterate(
-        const std::function<base::Result<void>(const Key& key, BpfMap<Key, Value>& map)>& filter) {
-    base::Result<Key> curKey = getFirstKey();
+Result<void> BpfMap<Key, Value>::iterate(
+        const function<Result<void>(const Key& key,
+                                    BpfMap<Key, Value>& map)>& filter) {
+    Result<Key> curKey = getFirstKey();
     while (curKey.ok()) {
-        const base::Result<Key>& nextKey = getNextKey(curKey.value());
-        base::Result<void> status = filter(curKey.value(), *this);
+        const Result<Key>& nextKey = getNextKey(curKey.value());
+        Result<void> status = filter(curKey.value(), *this);
         if (!status.ok()) return status;
         curKey = nextKey;
     }
@@ -294,15 +296,15 @@
 }
 
 template <class Key, class Value>
-base::Result<void> BpfMap<Key, Value>::iterateWithValue(
-        const std::function<base::Result<void>(const Key& key, const Value& value,
-                                               BpfMap<Key, Value>& map)>& filter) {
-    base::Result<Key> curKey = getFirstKey();
+Result<void> BpfMap<Key, Value>::iterateWithValue(
+        const function<Result<void>(const Key& key, const Value& value,
+                                    BpfMap<Key, Value>& map)>& filter) {
+    Result<Key> curKey = getFirstKey();
     while (curKey.ok()) {
-        const base::Result<Key>& nextKey = getNextKey(curKey.value());
-        base::Result<Value> curValue = readValue(curKey.value());
+        const Result<Key>& nextKey = getNextKey(curKey.value());
+        Result<Value> curValue = readValue(curKey.value());
         if (!curValue.ok()) return curValue.error();
-        base::Result<void> status = filter(curKey.value(), curValue.value(), *this);
+        Result<void> status = filter(curKey.value(), curValue.value(), *this);
         if (!status.ok()) return status;
         curKey = nextKey;
     }
@@ -319,7 +321,7 @@
         : BpfMap<Key, Value>(pathname, BPF_F_RDONLY) {}
 
     // Function that tries to get map from a pinned path.
-    [[clang::reinitializes]] base::Result<void> init(const char* path) {
+    [[clang::reinitializes]] Result<void> init(const char* path) {
         return BpfMap<Key,Value>::init(path, mapRetrieveRO(path));
     }
 };
diff --git a/staticlibs/netd/Android.bp b/staticlibs/netd/Android.bp
index 525160d..d135a1c 100644
--- a/staticlibs/netd/Android.bp
+++ b/staticlibs/netd/Android.bp
@@ -187,7 +187,7 @@
     srcs: [
         "binder/android/net/metrics/INetdEventListener.aidl",
     ],
-    versions: ["1"],
+
     backend: {
         ndk: {
             apex_available: [
@@ -205,6 +205,19 @@
             min_sdk_version: "29",
         },
     },
+    versions_with_info: [
+        {
+            version: "1",
+            imports: [],
+        },
+        {
+            version: "2",
+            imports: [],
+        },
+
+    ],
+    frozen: true,
+
 }
 
 java_library {
diff --git a/staticlibs/netd/aidl_api/netd_event_listener_interface/2/.hash b/staticlibs/netd/aidl_api/netd_event_listener_interface/2/.hash
new file mode 100644
index 0000000..67c55b7
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_event_listener_interface/2/.hash
@@ -0,0 +1 @@
+1b765b02815e970a124de92e793e42e0ceff5384
diff --git a/staticlibs/netd/aidl_api/netd_event_listener_interface/2/android/net/metrics/INetdEventListener.aidl b/staticlibs/netd/aidl_api/netd_event_listener_interface/2/android/net/metrics/INetdEventListener.aidl
new file mode 100644
index 0000000..1b0fe13
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_event_listener_interface/2/android/net/metrics/INetdEventListener.aidl
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.metrics;
+/* @hide */
+interface INetdEventListener {
+  oneway void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, @utf8InCpp String hostname, in @utf8InCpp String[] ipAddresses, int ipAddressesCount, int uid);
+  oneway void onPrivateDnsValidationEvent(int netId, String ipAddress, String hostname, boolean validated);
+  oneway void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, int uid);
+  oneway void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader, in byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs);
+  oneway void onTcpSocketStatsEvent(in int[] networkIds, in int[] sentPackets, in int[] lostPackets, in int[] rttUs, in int[] sentAckDiffMs);
+  oneway void onNat64PrefixEvent(int netId, boolean added, @utf8InCpp String prefixString, int prefixLength);
+  const int EVENT_GETADDRINFO = 1;
+  const int EVENT_GETHOSTBYNAME = 2;
+  const int EVENT_GETHOSTBYADDR = 3;
+  const int EVENT_RES_NSEND = 4;
+  const int REPORTING_LEVEL_NONE = 0;
+  const int REPORTING_LEVEL_METRICS = 1;
+  const int REPORTING_LEVEL_FULL = 2;
+  const int DNS_REPORTED_IP_ADDRESSES_LIMIT = 10;
+}
diff --git a/staticlibs/netd/libnetdutils/Android.bp b/staticlibs/netd/libnetdutils/Android.bp
index 08d5412..3169033 100644
--- a/staticlibs/netd/libnetdutils/Android.bp
+++ b/staticlibs/netd/libnetdutils/Android.bp
@@ -66,3 +66,8 @@
         "libbase",
     ],
 }
+
+cc_library_headers {
+    name: "libnetd_utils_headers",
+    export_include_dirs: ["include"],
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/ArpPacketTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/ArpPacketTest.java
new file mode 100644
index 0000000..e25d554
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/ArpPacketTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util;
+
+import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.net.InetAddresses;
+import android.net.MacAddress;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.arp.ArpPacket;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class ArpPacketTest {
+
+    private static final Inet4Address TEST_IPV4_ADDR =
+            (Inet4Address) InetAddresses.parseNumericAddress("192.168.1.2");
+    private static final Inet4Address INADDR_ANY =
+            (Inet4Address) InetAddresses.parseNumericAddress("0.0.0.0");
+    private static final byte[] TEST_SENDER_MAC_ADDR = new byte[] {
+            0x00, 0x1a, 0x11, 0x22, 0x33, 0x33 };
+    private static final byte[] TEST_TARGET_MAC_ADDR = new byte[] {
+            0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+    private static final byte[] TEST_ARP_PROBE = new byte[] {
+        // dst mac address
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+        // src mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+        // ether type
+        (byte) 0x08, (byte) 0x06,
+        // hardware type
+        (byte) 0x00, (byte) 0x01,
+        // protocol type
+        (byte) 0x08, (byte) 0x00,
+        // hardware address size
+        (byte) 0x06,
+        // protocol address size
+        (byte) 0x04,
+        // opcode
+        (byte) 0x00, (byte) 0x01,
+        // sender mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+        // sender IP address
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // target mac address
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // target IP address
+        (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02,
+    };
+
+    private static final byte[] TEST_ARP_ANNOUNCE = new byte[] {
+        // dst mac address
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+        // src mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+        // ether type
+        (byte) 0x08, (byte) 0x06,
+        // hardware type
+        (byte) 0x00, (byte) 0x01,
+        // protocol type
+        (byte) 0x08, (byte) 0x00,
+        // hardware address size
+        (byte) 0x06,
+        // protocol address size
+        (byte) 0x04,
+        // opcode
+        (byte) 0x00, (byte) 0x01,
+        // sender mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+        // sender IP address
+        (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02,
+        // target mac address
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        // target IP address
+        (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02,
+    };
+
+    private static final byte[] TEST_ARP_PROBE_TRUNCATED = new byte[] {
+        // dst mac address
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+        // src mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+        // ether type
+        (byte) 0x08, (byte) 0x06,
+        // hardware type
+        (byte) 0x00, (byte) 0x01,
+        // protocol type
+        (byte) 0x08, (byte) 0x00,
+        // hardware address size
+        (byte) 0x06,
+        // protocol address size
+        (byte) 0x04,
+        // opcode
+        (byte) 0x00,
+    };
+
+    private static final byte[] TEST_ARP_PROBE_TRUNCATED_MAC = new byte[] {
+         // dst mac address
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+        // src mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+        // ether type
+        (byte) 0x08, (byte) 0x06,
+        // hardware type
+        (byte) 0x00, (byte) 0x01,
+        // protocol type
+        (byte) 0x08, (byte) 0x00,
+        // hardware address size
+        (byte) 0x06,
+        // protocol address size
+        (byte) 0x04,
+        // opcode
+        (byte) 0x00, (byte) 0x01,
+        // sender mac address
+        (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33,
+    };
+
+    @Test
+    public void testBuildArpProbePacket() throws Exception {
+        final ByteBuffer arpProbe = ArpPacket.buildArpPacket(ETHER_BROADCAST,
+                TEST_SENDER_MAC_ADDR, TEST_IPV4_ADDR.getAddress(), new byte[ETHER_ADDR_LEN],
+                INADDR_ANY.getAddress(), (short) ARP_REQUEST);
+        assertArrayEquals(arpProbe.array(), TEST_ARP_PROBE);
+    }
+
+    @Test
+    public void testBuildArpAnnouncePacket() throws Exception {
+        final ByteBuffer arpAnnounce = ArpPacket.buildArpPacket(ETHER_BROADCAST,
+                TEST_SENDER_MAC_ADDR, TEST_IPV4_ADDR.getAddress(), new byte[ETHER_ADDR_LEN],
+                TEST_IPV4_ADDR.getAddress(), (short) ARP_REQUEST);
+        assertArrayEquals(arpAnnounce.array(), TEST_ARP_ANNOUNCE);
+    }
+
+    @Test
+    public void testParseArpProbePacket() throws Exception {
+        final ArpPacket packet = ArpPacket.parseArpPacket(TEST_ARP_PROBE, TEST_ARP_PROBE.length);
+        assertEquals(packet.opCode, ARP_REQUEST);
+        assertEquals(packet.senderHwAddress, MacAddress.fromBytes(TEST_SENDER_MAC_ADDR));
+        assertEquals(packet.targetHwAddress, MacAddress.fromBytes(TEST_TARGET_MAC_ADDR));
+        assertEquals(packet.senderIp, INADDR_ANY);
+        assertEquals(packet.targetIp, TEST_IPV4_ADDR);
+    }
+
+    @Test
+    public void testParseArpAnnouncePacket() throws Exception {
+        final ArpPacket packet = ArpPacket.parseArpPacket(TEST_ARP_ANNOUNCE,
+                TEST_ARP_ANNOUNCE.length);
+        assertEquals(packet.opCode, ARP_REQUEST);
+        assertEquals(packet.senderHwAddress, MacAddress.fromBytes(TEST_SENDER_MAC_ADDR));
+        assertEquals(packet.targetHwAddress, MacAddress.fromBytes(TEST_TARGET_MAC_ADDR));
+        assertEquals(packet.senderIp, TEST_IPV4_ADDR);
+        assertEquals(packet.targetIp, TEST_IPV4_ADDR);
+    }
+
+    @Test
+    public void testParseArpPacket_invalidByteBufferParameters() throws Exception {
+        assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket(
+                TEST_ARP_PROBE, 0));
+    }
+
+    @Test
+    public void testParseArpPacket_truncatedPacket() throws Exception {
+        assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket(
+                TEST_ARP_PROBE_TRUNCATED, TEST_ARP_PROBE_TRUNCATED.length));
+    }
+
+    @Test
+    public void testParseArpPacket_truncatedMacAddress() throws Exception {
+        assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket(
+                TEST_ARP_PROBE_TRUNCATED_MAC, TEST_ARP_PROBE_TRUNCATED.length));
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
index 9fb025b..e23f999 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
@@ -63,9 +63,6 @@
         assertEquals(0, CollectionUtils.indexOfSubArray(haystack, byteArrayOf()))
         assertEquals(-1, CollectionUtils.indexOfSubArray(byteArrayOf(), byteArrayOf(3, 2)))
         assertEquals(0, CollectionUtils.indexOfSubArray(byteArrayOf(), byteArrayOf()))
-        assertThrows(NullPointerException::class.java) {
-            CollectionUtils.indexOfSubArray(haystack, null)
-        }
     }
 
     @Test
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
index 7946244..f259e68 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
@@ -17,6 +17,7 @@
 package com.android.net.module.util;
 
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -233,7 +234,7 @@
                 eq(TEST_EXPERIMENT_FLAG)));
         assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG));
-        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+        assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
     }
 
@@ -243,9 +244,9 @@
                 eq(TEST_EXPERIMENT_FLAG)));
         assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG));
-        assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+        assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
-        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+        assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, true /* defaultEnabled */));
     }
 
@@ -258,7 +259,7 @@
                 eq(TEST_EXPERIMENT_FLAG)));
         assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG));
-        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+        assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
 
         // Feature should be disabled by flag value "999999999".
@@ -266,7 +267,7 @@
                 eq(TEST_EXPERIMENT_FLAG)));
         assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG));
-        assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+        assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
 
         // Follow defaultEnabled if the flag is not set
@@ -276,9 +277,9 @@
                 TEST_EXPERIMENT_FLAG, false /* defaultEnabled */));
         assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG, true /* defaultEnabled */));
-        assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+        assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
-        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+        assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, true /* defaultEnabled */));
     }
 
@@ -293,14 +294,14 @@
 
         assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG));
-        assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+        assertFalse(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
-        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+        assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, true /* defaultEnabled */));
 
         doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
                 eq(TEST_EXPERIMENT_FLAG)));
-        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+        assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
     }
 
@@ -323,9 +324,9 @@
     public void testFeatureIsEnabledCaching_APEX() throws Exception {
         doReturn(TEST_FLAG_VALUE_STRING).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
                 eq(TEST_EXPERIMENT_FLAG)));
-        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+        assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
-        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+        assertTrue(DeviceConfigUtils.isTetheringFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
 
         // Package info is only queried once
@@ -421,13 +422,25 @@
     }
 
     @Test
-    public void testIsTetheringFeatureForceDisabled() throws Exception {
+    public void testIsTetheringFeatureNotChickenedOut() throws Exception {
         doReturn("0").when(() -> DeviceConfig.getProperty(
                 eq(NAMESPACE_TETHERING), eq(TEST_EXPERIMENT_FLAG)));
-        assertFalse(DeviceConfigUtils.isTetheringFeatureForceDisabled(TEST_EXPERIMENT_FLAG));
+        assertTrue(DeviceConfigUtils.isTetheringFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG));
 
         doReturn(TEST_FLAG_VALUE_STRING).when(
                 () -> DeviceConfig.getProperty(eq(NAMESPACE_TETHERING), eq(TEST_EXPERIMENT_FLAG)));
-        assertTrue(DeviceConfigUtils.isTetheringFeatureForceDisabled(TEST_EXPERIMENT_FLAG));
+        assertFalse(DeviceConfigUtils.isTetheringFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG));
+    }
+
+    @Test
+    public void testIsNetworkStackFeatureNotChickenedOut() throws Exception {
+        doReturn("0").when(() -> DeviceConfig.getProperty(
+                eq(NAMESPACE_CONNECTIVITY), eq(TEST_EXPERIMENT_FLAG)));
+        assertTrue(DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG));
+
+        doReturn(TEST_FLAG_VALUE_STRING).when(
+                () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
+                                               eq(TEST_EXPERIMENT_FLAG)));
+        assertFalse(DeviceConfigUtils.isNetworkStackFeatureNotChickenedOut(TEST_EXPERIMENT_FLAG));
     }
 }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt
index 1b6cbcb..028308b 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt
@@ -16,96 +16,115 @@
 
 package com.android.net.module.util
 
+import android.Manifest.permission.INTERNET
+import android.Manifest.permission.NETWORK_SETTINGS
 import android.Manifest.permission.NETWORK_STACK
 import android.content.Context
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.PERMISSION_DENIED
 import android.content.pm.PackageManager.PERMISSION_GRANTED
 import android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+import android.os.Build
 import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
 import com.android.net.module.util.PermissionUtils.checkAnyPermissionOf
 import com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf
 import com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission
 import com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr
 import com.android.net.module.util.PermissionUtils.enforceSystemFeature
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
 import org.junit.Assert
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers
 import org.mockito.ArgumentMatchers.any
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.mock
-import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
 
 /** Tests for PermissionUtils */
-@RunWith(AndroidJUnit4::class)
+@RunWith(DevSdkIgnoreRunner::class)
 @SmallTest
 class PermissionUtilsTest {
+    @get:Rule
+    val ignoreRule = DevSdkIgnoreRule()
     private val TEST_PERMISSION1 = "android.permission.TEST_PERMISSION1"
     private val TEST_PERMISSION2 = "android.permission.TEST_PERMISSION2"
-    private val context = mock(Context::class.java)
-    private val packageManager = mock(PackageManager::class.java)
+    private val mockContext = mock(Context::class.java)
+    private val mockPackageManager = mock(PackageManager::class.java)
+
+    private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
 
     @Before
     fun setup() {
-        doReturn(packageManager).`when`(context).packageManager
+        doReturn(mockPackageManager).`when`(mockContext).packageManager
     }
 
     @Test
     fun testEnforceAnyPermissionOf() {
-        doReturn(PERMISSION_GRANTED).`when`(context).checkCallingOrSelfPermission(TEST_PERMISSION1)
-        doReturn(PERMISSION_DENIED).`when`(context).checkCallingOrSelfPermission(TEST_PERMISSION2)
-        assertTrue(checkAnyPermissionOf(context, TEST_PERMISSION1, TEST_PERMISSION2))
-        enforceAnyPermissionOf(context, TEST_PERMISSION1, TEST_PERMISSION2)
+        doReturn(PERMISSION_GRANTED).`when`(mockContext)
+            .checkCallingOrSelfPermission(TEST_PERMISSION1)
+        doReturn(PERMISSION_DENIED).`when`(mockContext)
+            .checkCallingOrSelfPermission(TEST_PERMISSION2)
+        assertTrue(checkAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2))
+        enforceAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2)
 
-        doReturn(PERMISSION_DENIED).`when`(context).checkCallingOrSelfPermission(TEST_PERMISSION1)
-        doReturn(PERMISSION_GRANTED).`when`(context).checkCallingOrSelfPermission(TEST_PERMISSION2)
-        assertTrue(checkAnyPermissionOf(context, TEST_PERMISSION1, TEST_PERMISSION2))
-        enforceAnyPermissionOf(context, TEST_PERMISSION1, TEST_PERMISSION2)
+        doReturn(PERMISSION_DENIED).`when`(mockContext)
+            .checkCallingOrSelfPermission(TEST_PERMISSION1)
+        doReturn(PERMISSION_GRANTED).`when`(mockContext)
+            .checkCallingOrSelfPermission(TEST_PERMISSION2)
+        assertTrue(checkAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2))
+        enforceAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2)
 
-        doReturn(PERMISSION_DENIED).`when`(context).checkCallingOrSelfPermission(any())
-        assertFalse(checkAnyPermissionOf(context, TEST_PERMISSION1, TEST_PERMISSION2))
+        doReturn(PERMISSION_DENIED).`when`(mockContext).checkCallingOrSelfPermission(any())
+        assertFalse(checkAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2))
         assertFailsWith<SecurityException>("Expect fail but permission granted.") {
-            enforceAnyPermissionOf(context, TEST_PERMISSION1, TEST_PERMISSION2) }
+            enforceAnyPermissionOf(mockContext, TEST_PERMISSION1, TEST_PERMISSION2)
+        }
     }
 
     @Test
     fun testEnforceNetworkStackPermissionOr() {
-        doReturn(PERMISSION_GRANTED).`when`(context).checkCallingOrSelfPermission(NETWORK_STACK)
-        doReturn(PERMISSION_DENIED).`when`(context)
-                .checkCallingOrSelfPermission(PERMISSION_MAINLINE_NETWORK_STACK)
-        enforceNetworkStackPermission(context)
-        enforceNetworkStackPermissionOr(context, TEST_PERMISSION1)
+        doReturn(PERMISSION_GRANTED).`when`(mockContext).checkCallingOrSelfPermission(NETWORK_STACK)
+        doReturn(PERMISSION_DENIED).`when`(mockContext)
+            .checkCallingOrSelfPermission(PERMISSION_MAINLINE_NETWORK_STACK)
+        enforceNetworkStackPermission(mockContext)
+        enforceNetworkStackPermissionOr(mockContext, TEST_PERMISSION1)
 
-        doReturn(PERMISSION_DENIED).`when`(context).checkCallingOrSelfPermission(NETWORK_STACK)
-        doReturn(PERMISSION_GRANTED).`when`(context)
-                .checkCallingOrSelfPermission(PERMISSION_MAINLINE_NETWORK_STACK)
-        enforceNetworkStackPermission(context)
-        enforceNetworkStackPermissionOr(context, TEST_PERMISSION2)
+        doReturn(PERMISSION_DENIED).`when`(mockContext).checkCallingOrSelfPermission(NETWORK_STACK)
+        doReturn(PERMISSION_GRANTED).`when`(mockContext)
+            .checkCallingOrSelfPermission(PERMISSION_MAINLINE_NETWORK_STACK)
+        enforceNetworkStackPermission(mockContext)
+        enforceNetworkStackPermissionOr(mockContext, TEST_PERMISSION2)
 
-        doReturn(PERMISSION_DENIED).`when`(context).checkCallingOrSelfPermission(NETWORK_STACK)
-        doReturn(PERMISSION_DENIED).`when`(context)
-                .checkCallingOrSelfPermission(PERMISSION_MAINLINE_NETWORK_STACK)
-        doReturn(PERMISSION_GRANTED).`when`(context).checkCallingOrSelfPermission(TEST_PERMISSION1)
+        doReturn(PERMISSION_DENIED).`when`(mockContext).checkCallingOrSelfPermission(NETWORK_STACK)
+        doReturn(PERMISSION_DENIED).`when`(mockContext)
+            .checkCallingOrSelfPermission(PERMISSION_MAINLINE_NETWORK_STACK)
+        doReturn(PERMISSION_GRANTED).`when`(mockContext)
+            .checkCallingOrSelfPermission(TEST_PERMISSION1)
         assertFailsWith<SecurityException>("Expect fail but permission granted.") {
-            enforceNetworkStackPermission(context) }
-        enforceNetworkStackPermissionOr(context, TEST_PERMISSION1)
+            enforceNetworkStackPermission(mockContext)
+        }
+        enforceNetworkStackPermissionOr(mockContext, TEST_PERMISSION1)
 
-        doReturn(PERMISSION_DENIED).`when`(context).checkCallingOrSelfPermission(any())
+        doReturn(PERMISSION_DENIED).`when`(mockContext).checkCallingOrSelfPermission(any())
         assertFailsWith<SecurityException>("Expect fail but permission granted.") {
-            enforceNetworkStackPermission(context) }
+            enforceNetworkStackPermission(mockContext)
+        }
         assertFailsWith<SecurityException>("Expect fail but permission granted.") {
-            enforceNetworkStackPermissionOr(context, TEST_PERMISSION2) }
+            enforceNetworkStackPermissionOr(mockContext, TEST_PERMISSION2)
+        }
     }
 
     private fun mockHasSystemFeature(featureName: String, hasFeature: Boolean) {
-        doReturn(hasFeature).`when`(packageManager)
-                .hasSystemFeature(ArgumentMatchers.eq(featureName))
+        doReturn(hasFeature).`when`(mockPackageManager)
+            .hasSystemFeature(ArgumentMatchers.eq(featureName))
     }
 
     @Test
@@ -114,14 +133,38 @@
         val exceptionMessage = "test exception message"
         mockHasSystemFeature(featureName = systemFeature, hasFeature = false)
         val e = assertFailsWith<UnsupportedOperationException>("Should fail without feature") {
-            enforceSystemFeature(context, systemFeature, exceptionMessage) }
+            enforceSystemFeature(mockContext, systemFeature, exceptionMessage)
+        }
         assertEquals(exceptionMessage, e.message)
 
         mockHasSystemFeature(featureName = systemFeature, hasFeature = true)
         try {
-            enforceSystemFeature(context, systemFeature, "")
+            enforceSystemFeature(mockContext, systemFeature, "")
         } catch (e: UnsupportedOperationException) {
             Assert.fail("Exception should have not been thrown with system feature enabled")
         }
     }
+
+    @Test
+    @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+    fun testIsSystemSignaturePermission() {
+        assertTrue(
+            PermissionUtils.isSystemSignaturePermission(
+                context,
+                NETWORK_SETTINGS
+            )
+        )
+        assertFalse(
+            PermissionUtils
+                .isSystemSignaturePermission(context, PERMISSION_MAINLINE_NETWORK_STACK)
+        )
+        assertFalse(
+            PermissionUtils
+                .isSystemSignaturePermission(context, "test_permission")
+        )
+        assertFalse(
+            PermissionUtils
+                .isSystemSignaturePermission(context, INTERNET)
+        )
+    }
 }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
index 4e46210..b4da043 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
@@ -89,20 +89,20 @@
         return Struct.parse(clazz, buf);
     }
 
-    static class HeaderMsgWithConstructor extends Struct {
+    public static class HeaderMsgWithConstructor extends Struct {
         static int sType;
         static int sLength;
 
         @Field(order = 0, type = Type.U8, padding = 1)
-        final short mFamily;
+        public final short mFamily;
         @Field(order = 1, type = Type.U16)
-        final int mLen;
+        public final int mLen;
         @Field(order = 2, type = Type.S32)
-        final int mIfindex;
+        public final int mIfindex;
         @Field(order = 3, type = Type.U8)
-        final short mIcmpType;
+        public final short mIcmpType;
         @Field(order = 4, type = Type.U8, padding = 6)
-        final short mIcmpCode;
+        public final short mIcmpCode;
 
         HeaderMsgWithConstructor(final short family, final int len, final int ifindex,
                 final short type, final short code) {
@@ -133,20 +133,20 @@
         verifyHeaderParsing(msg);
     }
 
-    static class HeaderMsgWithoutConstructor extends Struct {
+    public static class HeaderMsgWithoutConstructor extends Struct {
         static int sType;
         static int sLength;
 
         @Field(order = 0, type = Type.U8, padding = 1)
-        short mFamily;
+        public short mFamily;
         @Field(order = 1, type = Type.U16)
-        int mLen;
+        public int mLen;
         @Field(order = 2, type = Type.S32)
-        int mIfindex;
+        public int mIfindex;
         @Field(order = 3, type = Type.U8)
-        short mIcmpType;
+        public short mIcmpType;
         @Field(order = 4, type = Type.U8, padding = 6)
-        short mIcmpCode;
+        public short mIcmpCode;
     }
 
     @Test
@@ -164,7 +164,7 @@
                 msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
     }
 
-    static class HeaderMessage {
+    public static class HeaderMessage {
         @Field(order = 0, type = Type.U8, padding = 1)
         short mFamily;
         @Field(order = 1, type = Type.U16)
@@ -183,7 +183,7 @@
         assertThrows(IllegalArgumentException.class, () -> Struct.parse(HeaderMessage.class, buf));
     }
 
-    static class HeaderMessageMissingAnnotation extends Struct {
+    public static class HeaderMessageMissingAnnotation extends Struct {
         @Field(order = 0, type = Type.U8, padding = 1)
         short mFamily;
         @Field(order = 1, type = Type.U16)
@@ -202,15 +202,15 @@
                 () -> Struct.parse(HeaderMessageMissingAnnotation.class, buf));
     }
 
-    static class NetworkOrderMessage extends Struct {
+    public static class NetworkOrderMessage extends Struct {
         @Field(order = 0, type = Type.UBE16)
-        final int mUBE16;
+        public final int mUBE16;
         @Field(order = 1, type = Type.UBE32)
-        final long mUBE32;
+        public final long mUBE32;
         @Field(order = 2, type = Type.UBE64)
-        final BigInteger mUBE64;
+        public final BigInteger mUBE64;
         @Field(order = 3, type = Type.UBE63)
-        final long mUBE63;
+        public final long mUBE63;
 
         NetworkOrderMessage(final int be16, final long be32, final BigInteger be64,
                 final long be63) {
@@ -235,19 +235,19 @@
                 msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
     }
 
-    static class UnsignedDataMessage extends Struct {
+    public static class UnsignedDataMessage extends Struct {
         @Field(order = 0, type = Type.U8)
-        final short mU8;
+        public final short mU8;
         @Field(order = 1, type = Type.U16)
-        final int mU16;
+        public final int mU16;
         @Field(order = 2, type = Type.U32)
-        final long mU32;
+        public final long mU32;
         @Field(order = 3, type = Type.U64)
-        final BigInteger mU64;
+        public final BigInteger mU64;
         @Field(order = 4, type = Type.U63)
-        final long mU63;
+        public final long mU63;
         @Field(order = 5, type = Type.U63)
-        final long mLU64; // represent U64 data with U63 type
+        public final long mLU64; // represent U64 data with U63 type
 
         UnsignedDataMessage(final short u8, final int u16, final long u32, final BigInteger u64,
                 final long u63, final long lu64) {
@@ -276,7 +276,7 @@
                 msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
     }
 
-    static class U64DataMessage extends Struct {
+    public static class U64DataMessage extends Struct {
         @Field(order = 0, type = Type.U64) long mU64;
     }
 
@@ -290,10 +290,10 @@
     private static final String SMALL_VALUE_BIGINTEGER = "3412000000000000" + "0000000000001234"
             + "0000000000000000";
 
-    static class SmallValueBigInteger extends Struct {
-        @Field(order = 0, type = Type.U64) final BigInteger mSmallValue;
-        @Field(order = 1, type = Type.UBE64) final BigInteger mBSmallValue;
-        @Field(order = 2, type = Type.U64) final BigInteger mZero;
+    public static class SmallValueBigInteger extends Struct {
+        @Field(order = 0, type = Type.U64) public final BigInteger mSmallValue;
+        @Field(order = 1, type = Type.UBE64) public final BigInteger mBSmallValue;
+        @Field(order = 2, type = Type.U64) public final BigInteger mZero;
 
         SmallValueBigInteger(final BigInteger smallValue, final BigInteger bSmallValue,
                 final BigInteger zero) {
@@ -316,15 +316,15 @@
                 msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
     }
 
-    static class SignedDataMessage extends Struct {
+    public static class SignedDataMessage extends Struct {
         @Field(order = 0, type = Type.S8)
-        final byte mS8;
+        public final byte mS8;
         @Field(order = 1, type = Type.S16)
-        final short mS16;
+        public final short mS16;
         @Field(order = 2, type = Type.S32)
-        final int mS32;
+        public final int mS32;
         @Field(order = 3, type = Type.S64)
-        final long mS64;
+        public final long mS64;
 
         SignedDataMessage(final byte s8, final short s16, final int s32, final long s64) {
             mS8 = s8;
@@ -362,7 +362,7 @@
                 msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
     }
 
-    static class HeaderMessageWithDuplicateOrder extends Struct {
+    public static class HeaderMessageWithDuplicateOrder extends Struct {
         @Field(order = 0, type = Type.U8, padding = 1)
         short mFamily;
         @Field(order = 1, type = Type.U16)
@@ -382,7 +382,7 @@
                 () -> Struct.parse(HeaderMessageWithDuplicateOrder.class, buf));
     }
 
-    static class HeaderMessageWithNegativeOrder extends Struct {
+    public static class HeaderMessageWithNegativeOrder extends Struct {
         @Field(order = 0, type = Type.U8, padding = 1)
         short mFamily;
         @Field(order = 1, type = Type.U16)
@@ -402,7 +402,7 @@
                 () -> Struct.parse(HeaderMessageWithNegativeOrder.class, buf));
     }
 
-    static class HeaderMessageOutOfIndexBounds extends Struct {
+    public static class HeaderMessageOutOfIndexBounds extends Struct {
         @Field(order = 0, type = Type.U8, padding = 1)
         short mFamily;
         @Field(order = 1, type = Type.U16)
@@ -422,7 +422,7 @@
                 () -> Struct.parse(HeaderMessageOutOfIndexBounds.class, buf));
     }
 
-    static class HeaderMessageMismatchedPrimitiveType extends Struct {
+    public static class HeaderMessageMismatchedPrimitiveType extends Struct {
         @Field(order = 0, type = Type.U8, padding = 1)
         short mFamily;
         @Field(order = 1, type = Type.U16)
@@ -442,11 +442,11 @@
                 () -> Struct.parse(HeaderMessageMismatchedPrimitiveType.class, buf));
     }
 
-    static class PrefixMessage extends Struct {
+    public static class PrefixMessage extends Struct {
         @Field(order = 0, type = Type.UBE16)
-        final int mLifetime;
+        public final int mLifetime;
         @Field(order = 1, type = Type.ByteArray, arraysize = 12)
-        final byte[] mPrefix;
+        public final byte[] mPrefix;
 
         PrefixMessage(final int lifetime, final byte[] prefix) {
             mLifetime = lifetime;
@@ -469,7 +469,7 @@
                 msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
     }
 
-    static class PrefixMessageWithZeroLengthArray extends Struct {
+    public static class PrefixMessageWithZeroLengthArray extends Struct {
         @Field(order = 0, type = Type.UBE16)
         final int mLifetime;
         @Field(order = 1, type = Type.ByteArray, arraysize = 0)
@@ -495,7 +495,7 @@
         verifyPrefixByteArrayParsing(msg);
     }
 
-    static class HeaderMessageWithMutableField extends Struct {
+    public static class HeaderMessageWithMutableField extends Struct {
         @Field(order = 0, type = Type.U8, padding = 1)
         final short mFamily;
         @Field(order = 1, type = Type.U16)
@@ -521,20 +521,20 @@
                 () -> Struct.parse(HeaderMessageWithMutableField.class, buf));
     }
 
-    static class HeaderMsgWithStaticConstant extends Struct {
+    public static class HeaderMsgWithStaticConstant extends Struct {
         private static final String TAG = "HeaderMessage";
         private static final int FIELD_COUNT = 5;
 
         @Field(order = 0, type = Type.U8, padding = 1)
-        final short mFamily;
+        public final short mFamily;
         @Field(order = 1, type = Type.U16)
-        final int mLen;
+        public final int mLen;
         @Field(order = 2, type = Type.S32)
-        final int mIfindex;
+        public final int mIfindex;
         @Field(order = 3, type = Type.U8)
-        final short mIcmpType;
+        public final short mIcmpType;
         @Field(order = 4, type = Type.U8, padding = 6)
-        final short mIcmpCode;
+        public final short mIcmpCode;
 
         HeaderMsgWithStaticConstant(final short family, final int len, final int ifindex,
                 final short type, final short code) {
@@ -561,7 +561,7 @@
                 msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
     }
 
-    static class MismatchedConstructor extends Struct {
+    public static class MismatchedConstructor extends Struct {
         @Field(order = 0, type = Type.U16) final int mInt1;
         @Field(order = 1, type = Type.U16) final int mInt2;
         MismatchedConstructor(String int1, String int2) {
@@ -577,9 +577,9 @@
                 () -> Struct.parse(MismatchedConstructor.class, buf));
     }
 
-    static class ClassWithTwoConstructors extends Struct {
-        @Field(order = 0, type = Type.U16) final int mInt1;
-        @Field(order = 1, type = Type.U16) final int mInt2;
+    public static class ClassWithTwoConstructors extends Struct {
+        @Field(order = 0, type = Type.U16) public final int mInt1;
+        @Field(order = 1, type = Type.U16) public final int mInt2;
         ClassWithTwoConstructors(String int1, String int2) {
             mInt1 = Integer.valueOf(int1);
             mInt2 = Integer.valueOf(int2);
@@ -645,14 +645,14 @@
         }
     }
 
-    static class BigEndianDataMessage extends Struct {
-        @Field(order = 0, type = Type.S32) int mInt1;
-        @Field(order = 1, type = Type.S32) int mInt2;
-        @Field(order = 2, type = Type.UBE16) int mInt3;
-        @Field(order = 3, type = Type.U16) int mInt4;
-        @Field(order = 4, type = Type.U64) BigInteger mBigInteger1;
-        @Field(order = 5, type = Type.UBE64) BigInteger mBigInteger2;
-        @Field(order = 6, type = Type.S64) long mLong;
+    public static class BigEndianDataMessage extends Struct {
+        @Field(order = 0, type = Type.S32) public int mInt1;
+        @Field(order = 1, type = Type.S32) public int mInt2;
+        @Field(order = 2, type = Type.UBE16) public int mInt3;
+        @Field(order = 3, type = Type.U16) public int mInt4;
+        @Field(order = 4, type = Type.U64) public BigInteger mBigInteger1;
+        @Field(order = 5, type = Type.UBE64) public BigInteger mBigInteger2;
+        @Field(order = 6, type = Type.S64) public long mLong;
     }
 
     private static final String BIG_ENDIAN_DATA = "00000001" + "fffffffe" + "fffe" + "fffe"
@@ -680,9 +680,9 @@
         return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString));
     }
 
-    static class MacAddressMessage extends Struct {
-        @Field(order = 0, type = Type.EUI48) final MacAddress mMac1;
-        @Field(order = 1, type = Type.EUI48) final MacAddress mMac2;
+    public static class MacAddressMessage extends Struct {
+        public @Field(order = 0, type = Type.EUI48) final MacAddress mMac1;
+        public @Field(order = 1, type = Type.EUI48) final MacAddress mMac2;
 
         MacAddressMessage(final MacAddress mac1, final MacAddress mac2) {
             this.mMac1 = mac1;
@@ -703,7 +703,7 @@
                 msg.writeToBytes(ByteOrder.BIG_ENDIAN));
     }
 
-    static class BadMacAddressType extends Struct {
+    public static class BadMacAddressType extends Struct {
         @Field(order = 0, type = Type.EUI48) byte[] mMac;
     }
 
@@ -742,7 +742,7 @@
         assertArrayEquals(toByteBuffer(hexString).array(), msg.writeToBytes());
     }
 
-    static class IpAddressMessage extends Struct {
+    public static class IpAddressMessage extends Struct {
         @Field(order = 0, type = Type.Ipv4Address) public final Inet4Address ipv4Address;
         @Field(order = 1, type = Type.Ipv6Address) public final Inet6Address ipv6Address;
 
@@ -765,7 +765,7 @@
                 msg.writeToBytes(ByteOrder.BIG_ENDIAN));
     }
 
-    static class WrongIpAddressType extends Struct {
+    public static class WrongIpAddressType extends Struct {
         @Field(order = 0, type = Type.Ipv4Address) public byte[] ipv4Address;
         @Field(order = 1, type = Type.Ipv6Address) public byte[] ipv6Address;
     }
@@ -777,7 +777,7 @@
                                    toByteBuffer("c0a86401" + "20010db8000300040005000600070008")));
     }
 
-    static class FullTypeMessage extends Struct {
+    public static class FullTypeMessage extends Struct {
         @Field(order = 0, type = Type.U8) public final short u8;
         @Field(order = 1, type = Type.U16) public final int u16;
         @Field(order = 2, type = Type.U32) public final long u32;
@@ -886,7 +886,7 @@
         assertTrue(msg.equals(msg1));
     }
 
-    static class FullTypeMessageWithDupType extends Struct {
+    public static class FullTypeMessageWithDupType extends Struct {
         @Field(order = 0, type = Type.U8) public final short u8;
         @Field(order = 1, type = Type.U16) public final int u16;
         @Field(order = 2, type = Type.U32) public final long u32;
@@ -1029,7 +1029,7 @@
         assertEquals(msg.hashCode(), msg1.hashCode());
     }
 
-    static class InvalidByteArray extends Struct {
+    public static class InvalidByteArray extends Struct {
         @Field(order = 0, type = Type.ByteArray, arraysize = 12) public byte[] bytes;
     }
 
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
index 21e84da..35f22b9 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
@@ -19,11 +19,11 @@
 import android.os.Build
 import androidx.test.InstrumentationRegistry
 import com.android.modules.utils.build.UnboundedSdkLevel
+import java.util.regex.Pattern
 import org.junit.Assume.assumeTrue
 import org.junit.rules.TestRule
 import org.junit.runner.Description
 import org.junit.runners.model.Statement
-import java.util.regex.Pattern
 
 @Deprecated("Use Build.VERSION_CODES", ReplaceWith("Build.VERSION_CODES.S_V2"))
 const val SC_V2 = Build.VERSION_CODES.S_V2
@@ -32,8 +32,22 @@
 private val targetSdk = InstrumentationRegistry.getContext().applicationInfo.targetSdkVersion
 
 private fun isDevSdkInRange(minExclusive: String?, maxInclusive: String?): Boolean {
-    return (minExclusive == null || !UnboundedSdkLevel.isAtMost(minExclusive)) &&
-            (maxInclusive == null || UnboundedSdkLevel.isAtMost(maxInclusive))
+    return (minExclusive == null || !isAtMost(minExclusive)) &&
+            (maxInclusive == null || isAtMost(maxInclusive))
+}
+
+private fun isAtMost(sdkVersionOrCodename: String): Boolean {
+    // UnboundedSdkLevel does not support builds < Q, and may stop supporting Q as well since it
+    // is intended for mainline modules that are now R+.
+    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
+        // Assume that any codename passed as argument from current code is a more recent build than
+        // Q: this util did not exist before Q, and codenames are only used before the corresponding
+        // build is finalized. This util could list 28 older codenames to check against (as per
+        // ro.build.version.known_codenames in more recent builds), but this does not seem valuable.
+        val intVersion = sdkVersionOrCodename.toIntOrNull() ?: return true
+        return Build.VERSION.SDK_INT <= intVersion
+    }
+    return UnboundedSdkLevel.isAtMost(sdkVersionOrCodename)
 }
 
 /**
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/FakeDns.kt b/staticlibs/testutils/devicetests/com/android/testutils/FakeDns.kt
index 825d748..1f82a35 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/FakeDns.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/FakeDns.kt
@@ -81,9 +81,9 @@
         var type = if (posType != -1) it.arguments[posType] as Int else TYPE_UNSPECIFIED
         val answer = getAnswer(hostname, type)
 
-        if (!answer?.addresses.isNullOrEmpty()) {
+        if (answer != null && !answer.addresses.isNullOrEmpty()) {
             Handler(Looper.getMainLooper()).post({ executor.execute({
-                    callback.onAnswer(answer?.addresses, 0); }) })
+                    callback.onAnswer(answer.addresses, 0); }) })
         }
     }
 
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NonNullTestUtils.java b/staticlibs/testutils/devicetests/com/android/testutils/NonNullTestUtils.java
new file mode 100644
index 0000000..463c470
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/NonNullTestUtils.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils;
+
+import android.annotation.NonNull;
+
+/**
+ * Utilities to help Kotlin test to verify java @NonNull code
+ */
+public class NonNullTestUtils {
+    /**
+     * This method allows Kotlin to pass nullable to @NonNull java code for testing.
+     * For Foo(@NonNull arg) java method, Kotlin can pass nullable variable by
+     * Foo(NonNullTestUtils.nullUnsafe(nullableVar)).
+     */
+    @NonNull public static <T> T nullUnsafe(T v) {
+        return v;
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt b/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt
index da3508d..d50f78a 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt
@@ -48,8 +48,8 @@
     private val natMap = NatMap()
     private val binder = Binder()
 
-    private val cm = context.getSystemService(ConnectivityManager::class.java)
-    private val tnm = context.getSystemService(TestNetworkManager::class.java)
+    private val cm = context.getSystemService(ConnectivityManager::class.java)!!
+    private val tnm = context.getSystemService(TestNetworkManager::class.java)!!
 
     // Create test networks.
     private val internalIface = tnm.createTunInterface(listOf(internalAddr))
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt
index b743b6c..84fb47b 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt
@@ -91,7 +91,7 @@
     lp: LinkProperties?,
     setupTimeoutMs: Long = 10_000L
 ): TestNetworkTracker {
-    val tnm = context.getSystemService(TestNetworkManager::class.java)
+    val tnm = context.getSystemService(TestNetworkManager::class.java)!!
     val iface = if (isAtLeastS()) tnm.createTunInterface(linkAddrs)
     else tnm.createTunInterface(linkAddrs.toTypedArray())
     val lpWithIface = if (lp == null) null else LinkProperties(lp).apply {
@@ -112,7 +112,7 @@
     val lp: LinkProperties?,
     setupTimeoutMs: Long
 ) : TestableNetworkCallback.HasNetwork {
-    private val cm = context.getSystemService(ConnectivityManager::class.java)
+    private val cm = context.getSystemService(ConnectivityManager::class.java)!!
     private val binder = Binder()
 
     private val networkCallback: NetworkCallback
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
index 4b6aea2..df9c61a 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
@@ -389,7 +389,8 @@
         from: Int = mark,
         crossinline predicate: (T) -> Boolean = { true }
     ): T = history.poll(timeoutMs, from) { it is T && predicate(it) }.also {
-        assertNotNull(it, "Callback ${T::class} not received within ${timeoutMs}ms")
+        assertNotNull(it, "Callback ${T::class} not received within ${timeoutMs}ms. " +
+                "Got ${history.backtrace()}")
     } as T
 
     @JvmOverloads
@@ -398,7 +399,8 @@
         timeoutMs: Long = defaultTimeoutMs,
         predicate: (cb: T) -> Boolean = { true }
     ) = history.poll(timeoutMs) { type.java.isInstance(it) && predicate(it as T) }.also {
-        assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms")
+        assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms. " +
+                "Got ${history.backtrace()}")
     } as T
 
     fun <T : CallbackEntry> eventuallyExpect(
@@ -407,7 +409,8 @@
         from: Int = mark,
         predicate: (cb: T) -> Boolean = { true }
     ) = history.poll(timeoutMs, from) { type.java.isInstance(it) && predicate(it as T) }.also {
-        assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms")
+        assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms. " +
+                "Got ${history.backtrace()}")
     } as T
 
     // Expects onAvailable and the callbacks that follow it. These are:
diff --git a/staticlibs/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt b/staticlibs/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt
index efd77d1..f24e4f1 100644
--- a/staticlibs/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt
+++ b/staticlibs/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt
@@ -213,6 +213,9 @@
         private val slock = StampedLock()
         private var readHead = 0
 
+        // A special mark used to track the start of the last poll() operation.
+        private var pollMark = 0
+
         /**
          * @return the current value of the mark.
          */
@@ -223,6 +226,7 @@
             val stamp = slock.tryWriteLock()
             if (0L == stamp) concurrentAccessDetected()
             readHead = v
+            pollMark = v
             slock.unlockWrite(stamp)
         }
 
@@ -261,6 +265,7 @@
         fun poll(timeoutMs: Long, predicate: (E) -> Boolean = { true }): E? {
             val stamp = slock.tryWriteLock()
             if (0L == stamp) concurrentAccessDetected()
+            pollMark = readHead
             try {
                 lock.withLock {
                     val index = pollForIndexReadLocked(timeoutMs, readHead, predicate)
@@ -273,6 +278,25 @@
         }
 
         /**
+         * Returns a list of events that were observed since the last time poll() was called on this
+         * ReadHead.
+         *
+         * @return list of events since poll() was called.
+         */
+        fun backtrace(): List<E> {
+            val stamp = slock.tryReadLock()
+            if (0L == stamp) concurrentAccessDetected()
+
+            try {
+                lock.withLock {
+                    return ArrayList(subList(pollMark, mark))
+                }
+            } finally {
+                slock.unlockRead(stamp)
+            }
+        }
+
+        /**
          * Returns the first element after the mark or null. This never blocks.
          *
          * This method is subject to threading restrictions. It can be used concurrently on