Merge "Add CTS API coverage for APIs which are used by NetworkStack" into qt-dev
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 0e10de8..a69ca99 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -3449,6 +3449,10 @@
             final NetworkCallback callback;
             synchronized (sCallbacks) {
                 callback = sCallbacks.get(request);
+                if (message.what == CALLBACK_UNAVAIL) {
+                    sCallbacks.remove(request);
+                    callback.networkRequest = ALREADY_UNREGISTERED;
+                }
             }
             if (DBG) {
                 Log.d(TAG, getCallbackName(message.what) + " for network " + network);
@@ -3995,8 +3999,10 @@
         synchronized (sCallbacks) {
             Preconditions.checkArgument(networkCallback.networkRequest != null,
                     "NetworkCallback was not registered");
-            Preconditions.checkArgument(networkCallback.networkRequest != ALREADY_UNREGISTERED,
-                    "NetworkCallback was already unregistered");
+            if (networkCallback.networkRequest == ALREADY_UNREGISTERED) {
+                Log.d(TAG, "NetworkCallback was already unregistered");
+                return;
+            }
             for (Map.Entry<NetworkRequest, NetworkCallback> e : sCallbacks.entrySet()) {
                 if (e.getValue() == networkCallback) {
                     reqs.add(e.getKey());
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 28c59db..c5fc9b3 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -49,8 +49,8 @@
 namespace android {
 
 constexpr int MAXPACKETSIZE = 8 * 1024;
-// FrameworkListener limits the size of commands to 1024 bytes. TODO: fix this.
-constexpr int MAXCMDSIZE = 1024;
+// FrameworkListener limits the size of commands to 4096 bytes.
+constexpr int MAXCMDSIZE = 4096;
 
 static void throwErrnoException(JNIEnv* env, const char* functionName, int error) {
     ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName));
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 55f9826..facc299 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -40,7 +40,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkPolicyManager.RULE_NONE;
 import static android.net.NetworkPolicyManager.uidRulesToString;
-import static android.net.shared.NetworkMonitorUtils.isValidationRequired;
+import static android.net.shared.NetworkMonitorUtils.isPrivateDnsValidationRequired;
 import static android.os.Process.INVALID_UID;
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.IPPROTO_UDP;
@@ -2826,8 +2826,8 @@
         }
     }
 
-    private boolean networkRequiresValidation(NetworkAgentInfo nai) {
-        return isValidationRequired(nai.networkCapabilities);
+    private boolean networkRequiresPrivateDnsValidation(NetworkAgentInfo nai) {
+        return isPrivateDnsValidationRequired(nai.networkCapabilities);
     }
 
     private void handleFreshlyValidatedNetwork(NetworkAgentInfo nai) {
@@ -2845,7 +2845,7 @@
 
         for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
             handlePerNetworkPrivateDnsConfig(nai, cfg);
-            if (networkRequiresValidation(nai)) {
+            if (networkRequiresPrivateDnsValidation(nai)) {
                 handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
             }
         }
@@ -2854,7 +2854,7 @@
     private void handlePerNetworkPrivateDnsConfig(NetworkAgentInfo nai, PrivateDnsConfig cfg) {
         // Private DNS only ever applies to networks that might provide
         // Internet access and therefore also require validation.
-        if (!networkRequiresValidation(nai)) return;
+        if (!networkRequiresPrivateDnsValidation(nai)) return;
 
         // Notify the NetworkAgentInfo/NetworkMonitor in case NetworkMonitor needs to cancel or
         // schedule DNS resolutions. If a DNS resolution is required the
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index cfa9131..34772d0 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -483,11 +483,11 @@
         // down an explicitly selected network before the user gets a chance to prefer it when
         // a higher-scoring network (e.g., Ethernet) is available.
         if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {
-            return ConnectivityConstants.MAXIMUM_NETWORK_SCORE;
+            return ConnectivityConstants.EXPLICITLY_SELECTED_NETWORK_SCORE;
         }
 
         int score = currentScore;
-        if (!lastValidated && !pretendValidated && !ignoreWifiUnvalidationPenalty()) {
+        if (!lastValidated && !pretendValidated && !ignoreWifiUnvalidationPenalty() && !isVPN()) {
             score -= ConnectivityConstants.UNVALIDATED_SCORE_PENALTY;
         }
         if (score < 0) score = 0;
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index 709f5f6..e1c4238 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -46,28 +47,80 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class LinkPropertiesTest {
-    private static InetAddress ADDRV4 = NetworkUtils.numericToInetAddress("75.208.6.1");
-    private static InetAddress ADDRV6 = NetworkUtils.numericToInetAddress(
+    private static final InetAddress ADDRV4 = InetAddresses.parseNumericAddress("75.208.6.1");
+    private static final InetAddress ADDRV6 = InetAddresses.parseNumericAddress(
             "2001:0db8:85a3:0000:0000:8a2e:0370:7334");
-    private static InetAddress DNS1 = NetworkUtils.numericToInetAddress("75.208.7.1");
-    private static InetAddress DNS2 = NetworkUtils.numericToInetAddress("69.78.7.1");
-    private static InetAddress DNS6 = NetworkUtils.numericToInetAddress("2001:4860:4860::8888");
-    private static InetAddress PCSCFV6 =  NetworkUtils.numericToInetAddress(
+    private static final InetAddress DNS1 = InetAddresses.parseNumericAddress("75.208.7.1");
+    private static final InetAddress DNS2 = InetAddresses.parseNumericAddress("69.78.7.1");
+    private static final InetAddress DNS6 = InetAddresses.parseNumericAddress(
+            "2001:4860:4860::8888");
+    private static final InetAddress PRIVDNS1 = InetAddresses.parseNumericAddress("1.1.1.1");
+    private static final InetAddress PRIVDNS2 = InetAddresses.parseNumericAddress("1.0.0.1");
+    private static final InetAddress PRIVDNS6 = InetAddresses.parseNumericAddress(
+            "2606:4700:4700::1111");
+    private static final InetAddress PCSCFV4 = InetAddresses.parseNumericAddress("10.77.25.37");
+    private static final InetAddress PCSCFV6 = InetAddresses.parseNumericAddress(
             "2001:0db8:85a3:0000:0000:8a2e:0370:1");
-    private static InetAddress GATEWAY1 = NetworkUtils.numericToInetAddress("75.208.8.1");
-    private static InetAddress GATEWAY2 = NetworkUtils.numericToInetAddress("69.78.8.1");
-    private static InetAddress GATEWAY61 = NetworkUtils.numericToInetAddress("fe80::6:0000:613");
-    private static InetAddress GATEWAY62 = NetworkUtils.numericToInetAddress("fe80::6:2222");
-    private static String NAME = "qmi0";
-    private static int MTU = 1500;
-
-    private static LinkAddress LINKADDRV4 = new LinkAddress(ADDRV4, 32);
-    private static LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
-    private static LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64");
+    private static final InetAddress GATEWAY1 = InetAddresses.parseNumericAddress("75.208.8.1");
+    private static final InetAddress GATEWAY2 = InetAddresses.parseNumericAddress("69.78.8.1");
+    private static final InetAddress GATEWAY61 = InetAddresses.parseNumericAddress(
+            "fe80::6:0000:613");
+    private static final InetAddress GATEWAY62 = InetAddresses.parseNumericAddress("fe80::6:2222");
+    private static final String NAME = "qmi0";
+    private static final String DOMAINS = "google.com";
+    private static final String PRIV_DNS_SERVER_NAME = "private.dns.com";
+    private static final String TCP_BUFFER_SIZES = "524288,1048576,2097152,262144,524288,1048576";
+    private static final int MTU = 1500;
+    private static final LinkAddress LINKADDRV4 = new LinkAddress(ADDRV4, 32);
+    private static final LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
+    private static final LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64");
 
     // TODO: replace all calls to NetworkUtils.numericToInetAddress with calls to this method.
     private InetAddress Address(String addrString) {
-        return NetworkUtils.numericToInetAddress(addrString);
+        return InetAddresses.parseNumericAddress(addrString);
+    }
+
+    private void checkEmpty(final LinkProperties lp) {
+        assertEquals(0, lp.getAllInterfaceNames().size());
+        assertEquals(0, lp.getAllAddresses().size());
+        assertEquals(0, lp.getDnsServers().size());
+        assertEquals(0, lp.getValidatedPrivateDnsServers().size());
+        assertEquals(0, lp.getPcscfServers().size());
+        assertEquals(0, lp.getAllRoutes().size());
+        assertEquals(0, lp.getAllLinkAddresses().size());
+        assertEquals(0, lp.getStackedLinks().size());
+        assertEquals(0, lp.getMtu());
+        assertNull(lp.getPrivateDnsServerName());
+        assertNull(lp.getDomains());
+        assertNull(lp.getHttpProxy());
+        assertNull(lp.getTcpBufferSizes());
+        assertNull(lp.getNat64Prefix());
+        assertFalse(lp.isProvisioned());
+        assertFalse(lp.isIpv4Provisioned());
+        assertFalse(lp.isIpv6Provisioned());
+        assertFalse(lp.isPrivateDnsActive());
+    }
+
+    private LinkProperties makeTestObject() {
+        final LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(NAME);
+        lp.addLinkAddress(LINKADDRV4);
+        lp.addLinkAddress(LINKADDRV6);
+        lp.addDnsServer(DNS1);
+        lp.addDnsServer(DNS2);
+        lp.addValidatedPrivateDnsServer(PRIVDNS1);
+        lp.addValidatedPrivateDnsServer(PRIVDNS2);
+        lp.setUsePrivateDns(true);
+        lp.setPrivateDnsServerName(PRIV_DNS_SERVER_NAME);
+        lp.addPcscfServer(PCSCFV6);
+        lp.setDomains(DOMAINS);
+        lp.addRoute(new RouteInfo(GATEWAY1));
+        lp.addRoute(new RouteInfo(GATEWAY2));
+        lp.setHttpProxy(ProxyInfo.buildDirectProxy("test", 8888));
+        lp.setMtu(MTU);
+        lp.setTcpBufferSizes(TCP_BUFFER_SIZES);
+        lp.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96"));
+        return lp;
     }
 
     public void assertLinkPropertiesEqual(LinkProperties source, LinkProperties target) {
@@ -170,8 +223,7 @@
         target.clear();
         target.setInterfaceName(NAME);
         // change link addresses
-        target.addLinkAddress(new LinkAddress(
-                NetworkUtils.numericToInetAddress("75.208.6.2"), 32));
+        target.addLinkAddress(new LinkAddress(Address("75.208.6.2"), 32));
         target.addLinkAddress(LINKADDRV6);
         target.addDnsServer(DNS1);
         target.addDnsServer(DNS2);
@@ -186,7 +238,7 @@
         target.addLinkAddress(LINKADDRV4);
         target.addLinkAddress(LINKADDRV6);
         // change dnses
-        target.addDnsServer(NetworkUtils.numericToInetAddress("75.208.7.2"));
+        target.addDnsServer(Address("75.208.7.2"));
         target.addDnsServer(DNS2);
         target.addPcscfServer(PCSCFV6);
         target.addRoute(new RouteInfo(GATEWAY1));
@@ -198,11 +250,10 @@
         target.setInterfaceName(NAME);
         target.addLinkAddress(LINKADDRV4);
         target.addLinkAddress(LINKADDRV6);
-        target.addDnsServer(NetworkUtils.numericToInetAddress("75.208.7.2"));
+        target.addDnsServer(Address("75.208.7.2"));
         target.addDnsServer(DNS2);
         // change pcscf
-        target.addPcscfServer(NetworkUtils.numericToInetAddress(
-            "2001::1"));
+        target.addPcscfServer(Address("2001::1"));
         target.addRoute(new RouteInfo(GATEWAY1));
         target.addRoute(new RouteInfo(GATEWAY2));
         target.setMtu(MTU);
@@ -215,7 +266,7 @@
         target.addDnsServer(DNS1);
         target.addDnsServer(DNS2);
         // change gateway
-        target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress("75.208.8.2")));
+        target.addRoute(new RouteInfo(Address("75.208.8.2")));
         target.addRoute(new RouteInfo(GATEWAY2));
         target.setMtu(MTU);
         assertFalse(source.equals(target));
@@ -285,10 +336,15 @@
         }
     }
 
+    private void assertAllRoutesNotHaveInterface(String iface, LinkProperties lp) {
+        for (RouteInfo r : lp.getRoutes()) {
+            assertNotEquals(iface, r.getInterface());
+        }
+    }
+
     @Test
     public void testRouteInterfaces() {
-        LinkAddress prefix = new LinkAddress(
-            NetworkUtils.numericToInetAddress("2001:db8::"), 32);
+        LinkAddress prefix = new LinkAddress(Address("2001:db8::"), 32);
         InetAddress address = ADDRV6;
 
         // Add a route with no interface to a LinkProperties with no interface. No errors.
@@ -312,6 +368,8 @@
         // Change the interface name. All the routes should change their interface name too.
         lp.setInterfaceName("rmnet0");
         assertAllRoutesHaveInterface("rmnet0", lp);
+        assertAllRoutesNotHaveInterface(null, lp);
+        assertAllRoutesNotHaveInterface("wlan0", lp);
 
         // Now add a route with the wrong interface. This causes an exception too.
         try {
@@ -325,6 +383,7 @@
         lp.addRoute(r);
         assertEquals(2, lp.getRoutes().size());
         assertAllRoutesHaveInterface("wlan0", lp);
+        assertAllRoutesNotHaveInterface("rmnet0", lp);
 
         // Routes with null interfaces are converted to wlan0.
         r = RouteInfo.makeHostRoute(ADDRV6, null);
@@ -334,14 +393,23 @@
 
         // Check comparisons work.
         LinkProperties lp2 = new LinkProperties(lp);
-        assertAllRoutesHaveInterface("wlan0", lp);
+        assertAllRoutesHaveInterface("wlan0", lp2);
         assertEquals(0, lp.compareAllRoutes(lp2).added.size());
         assertEquals(0, lp.compareAllRoutes(lp2).removed.size());
 
         lp2.setInterfaceName("p2p0");
         assertAllRoutesHaveInterface("p2p0", lp2);
+        assertAllRoutesNotHaveInterface("wlan0", lp2);
         assertEquals(3, lp.compareAllRoutes(lp2).added.size());
         assertEquals(3, lp.compareAllRoutes(lp2).removed.size());
+
+        // Check remove works
+        lp.removeRoute(new RouteInfo(prefix, address, null));
+        assertEquals(3, lp.getRoutes().size());
+        lp.removeRoute(new RouteInfo(prefix, address, "wlan0"));
+        assertEquals(2, lp.getRoutes().size());
+        assertAllRoutesHaveInterface("wlan0", lp);
+        assertAllRoutesNotHaveInterface("p2p0", lp);
     }
 
     @Test
@@ -488,18 +556,26 @@
     }
 
     @Test
-    public void testSetLinkAddresses() {
-        LinkProperties lp = new LinkProperties();
+    public void testLinkAddresses() {
+        final LinkProperties lp = new LinkProperties();
         lp.addLinkAddress(LINKADDRV4);
         lp.addLinkAddress(LINKADDRV6);
 
-        LinkProperties lp2 = new LinkProperties();
+        final LinkProperties lp2 = new LinkProperties();
         lp2.addLinkAddress(LINKADDRV6);
 
-        assertFalse(lp.equals(lp2));
+        final LinkProperties lp3 = new LinkProperties();
+        final List<LinkAddress> linkAddresses = Arrays.asList(LINKADDRV4);
+        lp3.setLinkAddresses(linkAddresses);
 
-        lp2.setLinkAddresses(lp.getLinkAddresses());
-        assertTrue(lp.equals(lp));
+        assertFalse(lp.equals(lp2));
+        assertFalse(lp2.equals(lp3));
+
+        lp.removeLinkAddress(LINKADDRV4);
+        assertTrue(lp.equals(lp2));
+
+        lp2.setLinkAddresses(lp3.getLinkAddresses());
+        assertTrue(lp2.equals(lp3));
     }
 
     @Test
@@ -675,9 +751,9 @@
         assertTrue(v4lp.isReachable(DNS2));
 
         final LinkProperties v6lp = new LinkProperties();
-        final InetAddress kLinkLocalDns = NetworkUtils.numericToInetAddress("fe80::6:1");
-        final InetAddress kLinkLocalDnsWithScope = NetworkUtils.numericToInetAddress("fe80::6:2%43");
-        final InetAddress kOnLinkDns = NetworkUtils.numericToInetAddress("2001:db8:85a3::53");
+        final InetAddress kLinkLocalDns = Address("fe80::6:1");
+        final InetAddress kLinkLocalDnsWithScope = Address("fe80::6:2%43");
+        final InetAddress kOnLinkDns = Address("2001:db8:85a3::53");
         assertFalse(v6lp.isReachable(kLinkLocalDns));
         assertFalse(v6lp.isReachable(kLinkLocalDnsWithScope));
         assertFalse(v6lp.isReachable(kOnLinkDns));
@@ -686,8 +762,7 @@
         // Add a link-local route, making the link-local DNS servers reachable. Because
         // we assume the presence of an IPv6 link-local address, link-local DNS servers
         // are considered reachable, but only those with a non-zero scope identifier.
-        assertTrue(v6lp.addRoute(new RouteInfo(
-                new IpPrefix(NetworkUtils.numericToInetAddress("fe80::"), 64))));
+        assertTrue(v6lp.addRoute(new RouteInfo(new IpPrefix(Address("fe80::"), 64))));
         assertFalse(v6lp.isReachable(kLinkLocalDns));
         assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
         assertFalse(v6lp.isReachable(kOnLinkDns));
@@ -703,8 +778,7 @@
         // Add a global route on link, but no global address yet. DNS servers reachable
         // via a route that doesn't require a gateway: give them the benefit of the
         // doubt and hope the link-local source address suffices for communication.
-        assertTrue(v6lp.addRoute(new RouteInfo(
-                new IpPrefix(NetworkUtils.numericToInetAddress("2001:db8:85a3::"), 64))));
+        assertTrue(v6lp.addRoute(new RouteInfo(new IpPrefix(Address("2001:db8:85a3::"), 64))));
         assertFalse(v6lp.isReachable(kLinkLocalDns));
         assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
         assertTrue(v6lp.isReachable(kOnLinkDns));
@@ -766,8 +840,8 @@
         LinkProperties rmnet1 = new LinkProperties();
         rmnet1.setInterfaceName("rmnet1");
         rmnet1.addLinkAddress(new LinkAddress("10.0.0.3/8"));
-        RouteInfo defaultRoute1 = new RouteInfo((IpPrefix) null,
-                NetworkUtils.numericToInetAddress("10.0.0.1"), rmnet1.getInterfaceName());
+        RouteInfo defaultRoute1 = new RouteInfo((IpPrefix) null, Address("10.0.0.1"),
+                rmnet1.getInterfaceName());
         RouteInfo directRoute1 = new RouteInfo(new IpPrefix("10.0.0.0/8"), null,
                 rmnet1.getInterfaceName());
         rmnet1.addRoute(defaultRoute1);
@@ -785,8 +859,8 @@
         rmnet2.setInterfaceName("rmnet2");
         rmnet2.addLinkAddress(new LinkAddress("fe80::cafe/64"));
         rmnet2.addLinkAddress(new LinkAddress("2001:db8::2/64"));
-        RouteInfo defaultRoute2 = new RouteInfo((IpPrefix) null,
-                NetworkUtils.numericToInetAddress("2001:db8::1"), rmnet2.getInterfaceName());
+        RouteInfo defaultRoute2 = new RouteInfo((IpPrefix) null, Address("2001:db8::1"),
+                rmnet2.getInterfaceName());
         RouteInfo directRoute2 = new RouteInfo(new IpPrefix("2001:db8::/64"), null,
                 rmnet2.getInterfaceName());
         RouteInfo linkLocalRoute2 = new RouteInfo(new IpPrefix("fe80::/64"), null,
@@ -876,4 +950,111 @@
         LinkProperties empty = new LinkProperties();
         TestUtils.assertParcelingIsLossless(empty);
     }
+
+    @Test
+    public void testConstructor() {
+        LinkProperties lp = new LinkProperties();
+        checkEmpty(lp);
+        assertLinkPropertiesEqual(lp, new LinkProperties(lp));
+        assertLinkPropertiesEqual(lp, new LinkProperties());
+
+        lp = makeTestObject();
+        assertLinkPropertiesEqual(lp, new LinkProperties(lp));
+    }
+
+    @Test
+    public void testDnsServers() {
+        final LinkProperties lp = new LinkProperties();
+        final List<InetAddress> dnsServers = Arrays.asList(DNS1, DNS2);
+        lp.setDnsServers(dnsServers);
+        assertEquals(2, lp.getDnsServers().size());
+        assertEquals(DNS1, lp.getDnsServers().get(0));
+        assertEquals(DNS2, lp.getDnsServers().get(1));
+
+        lp.removeDnsServer(DNS1);
+        assertEquals(1, lp.getDnsServers().size());
+        assertEquals(DNS2, lp.getDnsServers().get(0));
+
+        lp.addDnsServer(DNS6);
+        assertEquals(2, lp.getDnsServers().size());
+        assertEquals(DNS2, lp.getDnsServers().get(0));
+        assertEquals(DNS6, lp.getDnsServers().get(1));
+    }
+
+    @Test
+    public void testValidatedPrivateDnsServers() {
+        final LinkProperties lp = new LinkProperties();
+        final List<InetAddress> privDnsServers = Arrays.asList(PRIVDNS1, PRIVDNS2);
+        lp.setValidatedPrivateDnsServers(privDnsServers);
+        assertEquals(2, lp.getValidatedPrivateDnsServers().size());
+        assertEquals(PRIVDNS1, lp.getValidatedPrivateDnsServers().get(0));
+        assertEquals(PRIVDNS2, lp.getValidatedPrivateDnsServers().get(1));
+
+        lp.removeValidatedPrivateDnsServer(PRIVDNS1);
+        assertEquals(1, lp.getValidatedPrivateDnsServers().size());
+        assertEquals(PRIVDNS2, lp.getValidatedPrivateDnsServers().get(0));
+
+        lp.addValidatedPrivateDnsServer(PRIVDNS6);
+        assertEquals(2, lp.getValidatedPrivateDnsServers().size());
+        assertEquals(PRIVDNS2, lp.getValidatedPrivateDnsServers().get(0));
+        assertEquals(PRIVDNS6, lp.getValidatedPrivateDnsServers().get(1));
+    }
+
+    @Test
+    public void testPcscfServers() {
+        final LinkProperties lp = new LinkProperties();
+        final List<InetAddress> pcscfServers = Arrays.asList(PCSCFV4);
+        lp.setPcscfServers(pcscfServers);
+        assertEquals(1, lp.getPcscfServers().size());
+        assertEquals(PCSCFV4, lp.getPcscfServers().get(0));
+
+        lp.removePcscfServer(PCSCFV4);
+        assertEquals(0, lp.getPcscfServers().size());
+
+        lp.addPcscfServer(PCSCFV6);
+        assertEquals(1, lp.getPcscfServers().size());
+        assertEquals(PCSCFV6, lp.getPcscfServers().get(0));
+    }
+
+    @Test
+    public void testTcpBufferSizes() {
+        final LinkProperties lp = makeTestObject();
+        assertEquals(TCP_BUFFER_SIZES, lp.getTcpBufferSizes());
+
+        lp.setTcpBufferSizes(null);
+        assertNull(lp.getTcpBufferSizes());
+    }
+
+    @Test
+    public void testHasIpv6DefaultRoute() {
+        final LinkProperties lp = makeTestObject();
+        assertFalse(lp.hasIPv6DefaultRoute());
+
+        lp.addRoute(new RouteInfo(GATEWAY61));
+        assertTrue(lp.hasIPv6DefaultRoute());
+    }
+
+    @Test
+    public void testHttpProxy() {
+        final LinkProperties lp = makeTestObject();
+        assertTrue(lp.getHttpProxy().equals(ProxyInfo.buildDirectProxy("test", 8888)));
+    }
+
+    @Test
+    public void testPrivateDnsServerName() {
+        final LinkProperties lp = makeTestObject();
+        assertEquals(PRIV_DNS_SERVER_NAME, lp.getPrivateDnsServerName());
+
+        lp.setPrivateDnsServerName(null);
+        assertNull(lp.getPrivateDnsServerName());
+    }
+
+    @Test
+    public void testUsePrivateDns() {
+        final LinkProperties lp = makeTestObject();
+        assertTrue(lp.isPrivateDnsActive());
+
+        lp.clear();
+        assertFalse(lp.isPrivateDnsActive());
+    }
 }
diff --git a/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
index 3ed8a86..0ce7c91 100644
--- a/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java
@@ -17,7 +17,9 @@
 package android.net.apf;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -32,8 +34,12 @@
 @SmallTest
 public class ApfCapabilitiesTest {
     @Test
-    public void testParcelUnparcel() {
+    public void testConstructAndParcel() {
         final ApfCapabilities caps = new ApfCapabilities(123, 456, 789);
+        assertEquals(123, caps.apfVersionSupported);
+        assertEquals(456, caps.maximumApfProgramSize);
+        assertEquals(789, caps.apfPacketFormat);
+
         ParcelableTestUtil.assertFieldCountEquals(3, ApfCapabilities.class);
 
         TestUtils.assertParcelingIsLossless(caps);
@@ -46,4 +52,14 @@
         assertNotEquals(new ApfCapabilities(1, 3, 3), new ApfCapabilities(1, 2, 3));
         assertNotEquals(new ApfCapabilities(1, 2, 4), new ApfCapabilities(1, 2, 3));
     }
+
+    @Test
+    public void testHasDataAccess() {
+        //hasDataAccess is only supported starting at apf version 4.
+        ApfCapabilities caps = new ApfCapabilities(1 /* apfVersionSupported */, 2, 3);
+        assertFalse(caps.hasDataAccess());
+
+        caps = new ApfCapabilities(4 /* apfVersionSupported */, 5, 6);
+        assertTrue(caps.hasDataAccess());
+    }
 }
diff --git a/tests/net/java/android/net/IpMemoryStoreTest.java b/tests/net/java/android/net/IpMemoryStoreTest.java
index 18c6768..8ff2de9 100644
--- a/tests/net/java/android/net/IpMemoryStoreTest.java
+++ b/tests/net/java/android/net/IpMemoryStoreTest.java
@@ -16,10 +16,26 @@
 
 package android.net;
 
-import static org.mockito.ArgumentMatchers.any;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.content.Context;
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.IOnStatusListener;
+import android.net.ipmemorystore.NetworkAttributes;
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.Status;
+import android.os.RemoteException;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -27,28 +43,57 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class IpMemoryStoreTest {
+    private static final String TAG = IpMemoryStoreTest.class.getSimpleName();
+    private static final String TEST_CLIENT_ID = "testClientId";
+    private static final String TEST_DATA_NAME = "testData";
+    private static final String TEST_OTHER_DATA_NAME = TEST_DATA_NAME + "Other";
+    private static final byte[] TEST_BLOB_DATA = new byte[] { -3, 6, 8, -9, 12,
+            -128, 0, 89, 112, 91, -34 };
+    private static final NetworkAttributes TEST_NETWORK_ATTRIBUTES = buildTestNetworkAttributes(
+            "hint", 219);
+
     @Mock
     Context mMockContext;
     @Mock
     NetworkStackClient mNetworkStackClient;
     @Mock
     IIpMemoryStore mMockService;
+    @Mock
+    IOnStatusListener mIOnStatusListener;
     IpMemoryStore mStore;
 
+    @Captor
+    ArgumentCaptor<IIpMemoryStoreCallbacks> mCbCaptor;
+    @Captor
+    ArgumentCaptor<NetworkAttributesParcelable> mNapCaptor;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        doAnswer(invocation -> {
-            ((IIpMemoryStoreCallbacks) invocation.getArgument(0))
-                    .onIpMemoryStoreFetched(mMockService);
-            return null;
-        }).when(mNetworkStackClient).fetchIpMemoryStore(any());
+    }
+
+    private void startIpMemoryStore(boolean supplyService) {
+        if (supplyService) {
+            doAnswer(invocation -> {
+                ((IIpMemoryStoreCallbacks) invocation.getArgument(0))
+                        .onIpMemoryStoreFetched(mMockService);
+                return null;
+            }).when(mNetworkStackClient).fetchIpMemoryStore(any());
+        } else {
+            doNothing().when(mNetworkStackClient).fetchIpMemoryStore(mCbCaptor.capture());
+        }
         mStore = new IpMemoryStore(mMockContext) {
             @Override
             protected NetworkStackClient getNetworkStackClient() {
@@ -57,24 +102,228 @@
         };
     }
 
-    @Test
-    public void testNetworkAttributes() {
-        // TODO : implement this
+    private static NetworkAttributes buildTestNetworkAttributes(String hint, int mtu) {
+        return new NetworkAttributes.Builder()
+                .setGroupHint(hint)
+                .setMtu(mtu)
+                .build();
     }
 
     @Test
-    public void testPrivateData() {
-        // TODO : implement this
+    public void testNetworkAttributes() throws Exception {
+        startIpMemoryStore(true);
+        final String l2Key = "fakeKey";
+
+        mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
+                status -> {
+                    assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+                });
+        verify(mMockService, times(1)).storeNetworkAttributes(eq(l2Key),
+                mNapCaptor.capture(), any());
+        assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
+
+        mStore.retrieveNetworkAttributes(l2Key,
+                (status, key, attr) -> {
+                    assertTrue("Retrieve network attributes not successful : "
+                            + status.resultCode, status.isSuccess());
+                    assertEquals(l2Key, key);
+                    assertEquals(TEST_NETWORK_ATTRIBUTES, attr);
+                });
+
+        verify(mMockService, times(1)).retrieveNetworkAttributes(eq(l2Key), any());
     }
 
     @Test
-    public void testFindL2Key() {
-        // TODO : implement this
+    public void testPrivateData() throws RemoteException {
+        startIpMemoryStore(true);
+        final Blob b = new Blob();
+        b.data = TEST_BLOB_DATA;
+        final String l2Key = "fakeKey";
+
+        mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+                status -> {
+                    assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+                });
+        verify(mMockService, times(1)).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
+                eq(b), any());
+
+        mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
+                (status, key, name, data) -> {
+                    assertTrue("Retrieve blob status not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(l2Key, key);
+                    assertEquals(name, TEST_DATA_NAME);
+                    assertTrue(Arrays.equals(b.data, data.data));
+                });
+        verify(mMockService, times(1)).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
+                eq(TEST_OTHER_DATA_NAME), any());
     }
 
     @Test
-    public void testIsSameNetwork() {
-        // TODO : implement this
+    public void testFindL2Key()
+            throws UnknownHostException, RemoteException, Exception {
+        startIpMemoryStore(true);
+        final String l2Key = "fakeKey";
+
+        mStore.findL2Key(TEST_NETWORK_ATTRIBUTES,
+                (status, key) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(l2Key, key);
+                });
+        verify(mMockService, times(1)).findL2Key(mNapCaptor.capture(), any());
+        assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
     }
 
+    @Test
+    public void testIsSameNetwork() throws UnknownHostException, RemoteException {
+        startIpMemoryStore(true);
+        final String l2Key1 = "fakeKey1";
+        final String l2Key2 = "fakeKey2";
+
+        mStore.isSameNetwork(l2Key1, l2Key2,
+                (status, answer) -> {
+                    assertFalse("Retrieve network sameness suspiciously successful : "
+                            + status.resultCode, status.isSuccess());
+                    assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+                    assertNull(answer);
+                });
+        verify(mMockService, times(1)).isSameNetwork(eq(l2Key1), eq(l2Key2), any());
+    }
+
+    @Test
+    public void testEnqueuedIpMsRequests() throws Exception {
+        startIpMemoryStore(false);
+
+        final Blob b = new Blob();
+        b.data = TEST_BLOB_DATA;
+        final String l2Key = "fakeKey";
+
+        // enqueue multiple ipms requests
+        mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
+                status -> {
+                    assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+                });
+        mStore.retrieveNetworkAttributes(l2Key,
+                (status, key, attr) -> {
+                    assertTrue("Retrieve network attributes not successful : "
+                            + status.resultCode, status.isSuccess());
+                    assertEquals(l2Key, key);
+                    assertEquals(TEST_NETWORK_ATTRIBUTES, attr);
+                });
+        mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+                status -> {
+                    assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+                });
+        mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
+                (status, key, name, data) -> {
+                    assertTrue("Retrieve blob status not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(l2Key, key);
+                    assertEquals(name, TEST_DATA_NAME);
+                    assertTrue(Arrays.equals(b.data, data.data));
+                });
+
+        // get ipms service ready
+        mCbCaptor.getValue().onIpMemoryStoreFetched(mMockService);
+
+        InOrder inOrder = inOrder(mMockService);
+
+        inOrder.verify(mMockService).storeNetworkAttributes(eq(l2Key), mNapCaptor.capture(), any());
+        inOrder.verify(mMockService).retrieveNetworkAttributes(eq(l2Key), any());
+        inOrder.verify(mMockService).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
+                eq(b), any());
+        inOrder.verify(mMockService).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
+                eq(TEST_OTHER_DATA_NAME), any());
+        assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
+    }
+
+    @Test
+    public void testEnqueuedIpMsRequestsWithException() throws Exception {
+        startIpMemoryStore(true);
+        doThrow(RemoteException.class).when(mMockService).retrieveNetworkAttributes(any(), any());
+
+        final Blob b = new Blob();
+        b.data = TEST_BLOB_DATA;
+        final String l2Key = "fakeKey";
+
+        // enqueue multiple ipms requests
+        mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
+                status -> {
+                    assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+                });
+        mStore.retrieveNetworkAttributes(l2Key,
+                (status, key, attr) -> {
+                    assertTrue("Retrieve network attributes not successful : "
+                            + status.resultCode, status.isSuccess());
+                    assertEquals(l2Key, key);
+                    assertEquals(TEST_NETWORK_ATTRIBUTES, attr);
+                });
+        mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+                status -> {
+                    assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+                });
+        mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
+                (status, key, name, data) -> {
+                    assertTrue("Retrieve blob status not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(l2Key, key);
+                    assertEquals(name, TEST_DATA_NAME);
+                    assertTrue(Arrays.equals(b.data, data.data));
+                });
+
+        // verify the rest of the queue is still processed in order even if the remote exception
+        // occurs when calling one or more requests
+        InOrder inOrder = inOrder(mMockService);
+
+        inOrder.verify(mMockService).storeNetworkAttributes(eq(l2Key), mNapCaptor.capture(), any());
+        inOrder.verify(mMockService).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
+                eq(b), any());
+        inOrder.verify(mMockService).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
+                eq(TEST_OTHER_DATA_NAME), any());
+        assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
+    }
+
+    @Test
+    public void testEnqueuedIpMsRequestsCallbackFunctionWithException() throws Exception {
+        startIpMemoryStore(true);
+
+        final Blob b = new Blob();
+        b.data = TEST_BLOB_DATA;
+        final String l2Key = "fakeKey";
+
+        // enqueue multiple ipms requests
+        mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
+                status -> {
+                    assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+                });
+        mStore.retrieveNetworkAttributes(l2Key,
+                (status, key, attr) -> {
+                    throw new RuntimeException("retrieveNetworkAttributes test");
+                });
+        mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+                status -> {
+                    throw new RuntimeException("storeBlob test");
+                });
+        mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
+                (status, key, name, data) -> {
+                    assertTrue("Retrieve blob status not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(l2Key, key);
+                    assertEquals(name, TEST_DATA_NAME);
+                    assertTrue(Arrays.equals(b.data, data.data));
+                });
+
+        // verify the rest of the queue is still processed in order even if when one or more
+        // callback throw the remote exception
+        InOrder inOrder = inOrder(mMockService);
+
+        inOrder.verify(mMockService).storeNetworkAttributes(eq(l2Key), mNapCaptor.capture(),
+                any());
+        inOrder.verify(mMockService).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
+                eq(b), any());
+        inOrder.verify(mMockService).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
+                eq(TEST_OTHER_DATA_NAME), any());
+        assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
+    }
 }
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 363ac9c..fe13787 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -28,6 +28,7 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
 import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
 import static android.net.ConnectivityManager.TYPE_NONE;
+import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
 import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
@@ -489,7 +490,7 @@
 
         MockNetworkAgent(int transport, LinkProperties linkProperties) {
             final int type = transportToLegacyType(transport);
-            final String typeName = ConnectivityManager.getNetworkTypeName(transport);
+            final String typeName = ConnectivityManager.getNetworkTypeName(type);
             mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock");
             mNetworkCapabilities = new NetworkCapabilities();
             mNetworkCapabilities.addTransportType(transport);
@@ -619,6 +620,10 @@
             mNetworkAgent.sendNetworkScore(mScore);
         }
 
+        public int getScore() {
+            return mScore;
+        }
+
         public void explicitlySelected(boolean acceptUnvalidated) {
             mNetworkAgent.explicitlySelected(acceptUnvalidated);
         }
@@ -1330,6 +1335,8 @@
                 return TYPE_WIFI;
             case TRANSPORT_CELLULAR:
                 return TYPE_MOBILE;
+            case TRANSPORT_VPN:
+                return TYPE_VPN;
             default:
                 return TYPE_NONE;
         }
@@ -3854,6 +3861,9 @@
         networkCallback.expectCallback(CallbackState.UNAVAILABLE, null);
         testFactory.waitForRequests();
 
+        // unregister network callback - a no-op, but should not fail
+        mCm.unregisterNetworkCallback(networkCallback);
+
         testFactory.unregister();
         handlerThread.quit();
     }
@@ -5365,6 +5375,58 @@
     }
 
     @Test
+    public void testVpnUnvalidated() throws Exception {
+        final TestNetworkCallback callback = new TestNetworkCallback();
+        mCm.registerDefaultNetworkCallback(callback);
+
+        // Bring up Ethernet.
+        mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
+        mEthernetNetworkAgent.connect(true);
+        callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
+        callback.assertNoCallback();
+
+        // Bring up a VPN that has the INTERNET capability, initially unvalidated.
+        final int uid = Process.myUid();
+        final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN);
+        final ArraySet<UidRange> ranges = new ArraySet<>();
+        ranges.add(new UidRange(uid, uid));
+        mMockVpn.setNetworkAgent(vpnNetworkAgent);
+        mMockVpn.setUids(ranges);
+        vpnNetworkAgent.connect(false /* validated */, true /* hasInternet */);
+        mMockVpn.connect();
+
+        // Even though the VPN is unvalidated, it becomes the default network for our app.
+        callback.expectAvailableCallbacksUnvalidated(vpnNetworkAgent);
+        // TODO: this looks like a spurious callback.
+        callback.expectCallback(CallbackState.NETWORK_CAPABILITIES, vpnNetworkAgent);
+        callback.assertNoCallback();
+
+        assertTrue(vpnNetworkAgent.getScore() > mEthernetNetworkAgent.getScore());
+        assertEquals(ConnectivityConstants.VPN_DEFAULT_SCORE, vpnNetworkAgent.getScore());
+        assertEquals(vpnNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+        NetworkCapabilities nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());
+        assertFalse(nc.hasCapability(NET_CAPABILITY_VALIDATED));
+        assertTrue(nc.hasCapability(NET_CAPABILITY_INTERNET));
+
+        assertFalse(NetworkMonitorUtils.isValidationRequired(vpnNetworkAgent.mNetworkCapabilities));
+        assertTrue(NetworkMonitorUtils.isPrivateDnsValidationRequired(
+                vpnNetworkAgent.mNetworkCapabilities));
+
+        // Pretend that the VPN network validates.
+        vpnNetworkAgent.setNetworkValid();
+        vpnNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+        // Expect to see the validated capability, but no other changes, because the VPN is already
+        // the default network for the app.
+        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, vpnNetworkAgent);
+        callback.assertNoCallback();
+
+        vpnNetworkAgent.disconnect();
+        callback.expectCallback(CallbackState.LOST, vpnNetworkAgent);
+        callback.expectAvailableCallbacksValidated(mEthernetNetworkAgent);
+    }
+
+    @Test
     public void testVpnSetUnderlyingNetworks() {
         final int uid = Process.myUid();
 
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 7c40adf..71b72b8 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -32,6 +32,7 @@
 
 import android.app.AppOpsManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.net.INetd;
 import android.net.IpSecAlgorithm;
 import android.net.IpSecConfig;
@@ -57,6 +58,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
+import java.net.Inet4Address;
 import java.net.Socket;
 import java.util.Arrays;
 import java.util.Collection;
@@ -119,6 +121,11 @@
         }
 
         @Override
+        public PackageManager getPackageManager() {
+            return mMockPkgMgr;
+        }
+
+        @Override
         public void enforceCallingOrSelfPermission(String permission, String message) {
             if (permission == android.Manifest.permission.MANAGE_IPSEC_TUNNELS) {
                 return;
@@ -128,6 +135,7 @@
     };
 
     INetd mMockNetd;
+    PackageManager mMockPkgMgr;
     IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
     IpSecService mIpSecService;
     Network fakeNetwork = new Network(0xAB);
@@ -152,11 +160,16 @@
     @Before
     public void setUp() throws Exception {
         mMockNetd = mock(INetd.class);
+        mMockPkgMgr = mock(PackageManager.class);
         mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class);
         mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig);
 
         // Injecting mock netd
         when(mMockIpSecSrvConfig.getNetdInstance()).thenReturn(mMockNetd);
+
+        // PackageManager should always return true (feature flag tests in IpSecServiceTest)
+        when(mMockPkgMgr.hasSystemFeature(anyString())).thenReturn(true);
+
         // A package granted the AppOp for MANAGE_IPSEC_TUNNELS will be MODE_ALLOWED.
         when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("blessedPackage")))
             .thenReturn(AppOpsManager.MODE_ALLOWED);
@@ -709,4 +722,18 @@
         } catch (SecurityException expected) {
         }
     }
+
+    @Test
+    public void testFeatureFlagVerification() throws Exception {
+        when(mMockPkgMgr.hasSystemFeature(eq(PackageManager.FEATURE_IPSEC_TUNNELS)))
+                .thenReturn(false);
+
+        try {
+            String addr = Inet4Address.getLoopbackAddress().getHostAddress();
+            mIpSecService.createTunnelInterface(
+                    addr, addr, new Network(0), new Binder(), "blessedPackage");
+            fail("Expected UnsupportedOperationException for disabled feature");
+        } catch (UnsupportedOperationException expected) {
+        }
+    }
 }
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
index a83faf3..fb84611 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.connectivity.ipmemorystore;
+package com.android.server.net.ipmemorystore;
 
 import static org.junit.Assert.assertEquals;