Teach LinkProperties whether it's provisioned or not.

In IPv4, a link is provisioned when DHCP succeeds. In IPv6, a
there is no such signal, because addresses and DNS servers can
be notified by the kernel at different times.

Add an isProvisioned method that returns true if we believe that
enough information has configured to use a network. For IPv6,
this requires an IP address, default route, and DNS server. For
IPv4, this requires only an IPv4 address, because we support
static configuration that doesn't have a default route or DNS
server.

To do this we use the existing hasIPv4Address method, rename the
all-but unused hasIPv6Address method to hasGlobalIPv6Address
(which is what we want anyway) and add new hasIPv[46]DefaultRoute
and hasIPv[46]DnsServer methods.

Bug: 9180552
Change-Id: Ib2f5ff8af920f7b6f1edf0e2afaaa0edce9bc72d
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index e7184ed..2032db5 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -540,14 +540,14 @@
     }
 
     /**
-     * Returns true if this link has an IPv6 address.
+     * Returns true if this link has a global preferred IPv6 address.
      *
-     * @return {@code true} if there is an IPv6 address, {@code false} otherwise.
+     * @return {@code true} if there is a global preferred IPv6 address, {@code false} otherwise.
      * @hide
      */
-    public boolean hasIPv6Address() {
+    public boolean hasGlobalIPv6Address() {
         for (LinkAddress address : mLinkAddresses) {
-          if (address.getAddress() instanceof Inet6Address) {
+          if (address.getAddress() instanceof Inet6Address && address.isGlobalPreferred()) {
             return true;
           }
         }
@@ -555,6 +555,80 @@
     }
 
     /**
+     * Returns true if this link has an IPv4 default route.
+     *
+     * @return {@code true} if there is an IPv4 default route, {@code false} otherwise.
+     * @hide
+     */
+    public boolean hasIPv4DefaultRoute() {
+        for (RouteInfo r : mRoutes) {
+          if (r.isIPv4Default()) {
+            return true;
+          }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this link has an IPv6 default route.
+     *
+     * @return {@code true} if there is an IPv6 default route, {@code false} otherwise.
+     * @hide
+     */
+    public boolean hasIPv6DefaultRoute() {
+        for (RouteInfo r : mRoutes) {
+          if (r.isIPv6Default()) {
+            return true;
+          }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this link has an IPv4 DNS server.
+     *
+     * @return {@code true} if there is an IPv4 DNS server, {@code false} otherwise.
+     * @hide
+     */
+    public boolean hasIPv4DnsServer() {
+        for (InetAddress ia : mDnses) {
+          if (ia instanceof Inet4Address) {
+            return true;
+          }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this link has an IPv6 DNS server.
+     *
+     * @return {@code true} if there is an IPv6 DNS server, {@code false} otherwise.
+     * @hide
+     */
+    public boolean hasIPv6DnsServer() {
+        for (InetAddress ia : mDnses) {
+          if (ia instanceof Inet6Address) {
+            return true;
+          }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this link is provisioned for global connectivity. For IPv6, this requires an
+     * IP address, default route, and DNS server. For IPv4, this requires only an IPv4 address,
+     * because WifiStateMachine accepts static configurations that only specify an address but not
+     * DNS servers or a default route.
+     *
+     * @return {@code true} if the link is provisioned, {@code false} otherwise.
+     * @hide
+     */
+    public boolean isProvisioned() {
+        return (hasIPv4Address() ||
+                (hasGlobalIPv6Address() && hasIPv6DefaultRoute() && hasIPv6DnsServer()));
+    }
+
+    /**
      * Compares this {@code LinkProperties} interface name against the target
      *
      * @param target LinkProperties to compare.
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
index 9646510..4015b3d 100644
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -38,6 +38,7 @@
 
     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");
 
     public void assertLinkPropertiesEqual(LinkProperties source, LinkProperties target) {
         // Check implementation of equals(), element by element.
@@ -357,7 +358,7 @@
 
         // No addresses.
         assertFalse(lp.hasIPv4Address());
-        assertFalse(lp.hasIPv6Address());
+        assertFalse(lp.hasGlobalIPv6Address());
 
         // Addresses on stacked links don't count.
         LinkProperties stacked = new LinkProperties();
@@ -366,12 +367,12 @@
         stacked.addLinkAddress(LINKADDRV4);
         stacked.addLinkAddress(LINKADDRV6);
         assertTrue(stacked.hasIPv4Address());
-        assertTrue(stacked.hasIPv6Address());
+        assertTrue(stacked.hasGlobalIPv6Address());
         assertFalse(lp.hasIPv4Address());
-        assertFalse(lp.hasIPv6Address());
+        assertFalse(lp.hasGlobalIPv6Address());
         lp.removeStackedLink(stacked);
         assertFalse(lp.hasIPv4Address());
-        assertFalse(lp.hasIPv6Address());
+        assertFalse(lp.hasGlobalIPv6Address());
 
         // Addresses on the base link.
         // Check the return values of hasIPvXAddress and ensure the add/remove methods return true
@@ -380,19 +381,29 @@
         assertTrue(lp.addLinkAddress(LINKADDRV6));
         assertEquals(1, lp.getLinkAddresses().size());
         assertFalse(lp.hasIPv4Address());
-        assertTrue(lp.hasIPv6Address());
+        assertTrue(lp.hasGlobalIPv6Address());
 
         assertTrue(lp.removeLinkAddress(LINKADDRV6));
         assertEquals(0, lp.getLinkAddresses().size());
-        assertTrue(lp.addLinkAddress(LINKADDRV4));
-        assertEquals(1, lp.getLinkAddresses().size());
-        assertTrue(lp.hasIPv4Address());
-        assertFalse(lp.hasIPv6Address());
 
-        assertTrue(lp.addLinkAddress(LINKADDRV6));
+        assertTrue(lp.addLinkAddress(LINKADDRV6LINKLOCAL));
+        assertEquals(1, lp.getLinkAddresses().size());
+        assertFalse(lp.hasGlobalIPv6Address());
+
+        assertTrue(lp.addLinkAddress(LINKADDRV4));
         assertEquals(2, lp.getLinkAddresses().size());
         assertTrue(lp.hasIPv4Address());
-        assertTrue(lp.hasIPv6Address());
+        assertFalse(lp.hasGlobalIPv6Address());
+
+        assertTrue(lp.addLinkAddress(LINKADDRV6));
+        assertEquals(3, lp.getLinkAddresses().size());
+        assertTrue(lp.hasIPv4Address());
+        assertTrue(lp.hasGlobalIPv6Address());
+
+        assertTrue(lp.removeLinkAddress(LINKADDRV6LINKLOCAL));
+        assertEquals(2, lp.getLinkAddresses().size());
+        assertTrue(lp.hasIPv4Address());
+        assertTrue(lp.hasGlobalIPv6Address());
 
         // Adding an address twice has no effect.
         // Removing an address that's not present has no effect.
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 00bda06..2f5524e 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4713,7 +4713,7 @@
                         LinkProperties lp = mCs.getLinkPropertiesForTypeInternal(
                                 ConnectivityManager.TYPE_MOBILE_HIPRI);
                         boolean linkHasIpv4 = lp.hasIPv4Address();
-                        boolean linkHasIpv6 = lp.hasIPv6Address();
+                        boolean linkHasIpv6 = lp.hasGlobalIPv6Address();
                         log("isMobileOk: linkHasIpv4=" + linkHasIpv4
                                 + " linkHasIpv6=" + linkHasIpv6);