Define NOT_ROAMING network capability.

The "roaming" state of a network really belongs on NetworkCapabilities
instead of being published through NetworkInfo.isRoaming().  One major
reason is to support developers creating NetworkRequests for a
non-roaming network.

Watch for any capability changes that network statistics are
interested in (either metered or roaming) and notify it to perform
an update pass; fixes bug where we previously only triggered on
roaming changes.

Fix bug in VPNs where metered/roaming capabilities of underlying
networks weren't being propagated; this was probably preventing
some jobs from running over unmetered networks, and causing other
jobs to run over roaming networks!  Also passes along link bandwidth
information from underlying networks, and propegates any changes
to underlying networks.

Fix race condition by reading prevNc inside lock.  Utility methods
correctly calculate min/max link bandwidth values.

Test: bit FrameworksNetTests:android.net.,com.android.server.net.,com.android.server.connectivity.,com.android.server.ConnectivityServiceTest
Bug: 68397798, 16207332
Change-Id: I3e1a6544c902bf3a79356b72d3616af1fd2b0f49
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index a44b18d..7715727 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -18,6 +18,8 @@
 
 import static android.Manifest.permission.BIND_VPN_SERVICE;
 import static android.net.ConnectivityManager.NETID_UNSET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
 import static android.net.RouteInfo.RTN_THROW;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 
@@ -90,6 +92,8 @@
 import com.android.internal.net.VpnInfo;
 import com.android.internal.net.VpnProfile;
 import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.ConnectivityService;
 import com.android.server.DeviceIdleController;
 import com.android.server.LocalServices;
 import com.android.server.net.BaseNetworkObserver;
@@ -245,10 +249,10 @@
         }
 
         mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_VPN, 0, NETWORKTYPE, "");
-        // TODO: Copy metered attribute and bandwidths from physical transport, b/16207332
         mNetworkCapabilities = new NetworkCapabilities();
         mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
         mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
+        updateCapabilities();
 
         loadAlwaysOnPackage();
     }
@@ -275,6 +279,62 @@
         updateAlwaysOnNotification(detailedState);
     }
 
+    public void updateCapabilities() {
+        final Network[] underlyingNetworks = (mConfig != null) ? mConfig.underlyingNetworks : null;
+        updateCapabilities(mContext.getSystemService(ConnectivityManager.class), underlyingNetworks,
+                mNetworkCapabilities);
+
+        if (mNetworkAgent != null) {
+            mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+        }
+    }
+
+    @VisibleForTesting
+    public static void updateCapabilities(ConnectivityManager cm, Network[] underlyingNetworks,
+            NetworkCapabilities caps) {
+        int[] transportTypes = new int[] { NetworkCapabilities.TRANSPORT_VPN };
+        int downKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
+        int upKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
+        boolean metered = false;
+        boolean roaming = false;
+
+        if (ArrayUtils.isEmpty(underlyingNetworks)) {
+            // No idea what the underlying networks are; assume sane defaults
+            metered = true;
+            roaming = false;
+        } else {
+            for (Network underlying : underlyingNetworks) {
+                final NetworkCapabilities underlyingCaps = cm.getNetworkCapabilities(underlying);
+                for (int underlyingType : underlyingCaps.getTransportTypes()) {
+                    transportTypes = ArrayUtils.appendInt(transportTypes, underlyingType);
+                }
+
+                // When we have multiple networks, we have to assume the
+                // worst-case link speed and restrictions.
+                downKbps = NetworkCapabilities.minBandwidth(downKbps,
+                        underlyingCaps.getLinkDownstreamBandwidthKbps());
+                upKbps = NetworkCapabilities.minBandwidth(upKbps,
+                        underlyingCaps.getLinkUpstreamBandwidthKbps());
+                metered |= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_METERED);
+                roaming |= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+            }
+        }
+
+        caps.setTransportTypes(transportTypes);
+        caps.setLinkDownstreamBandwidthKbps(downKbps);
+        caps.setLinkUpstreamBandwidthKbps(upKbps);
+        if (metered) {
+            caps.removeCapability(NET_CAPABILITY_NOT_METERED);
+        } else {
+            caps.addCapability(NET_CAPABILITY_NOT_METERED);
+        }
+        if (roaming) {
+            caps.removeCapability(NET_CAPABILITY_NOT_ROAMING);
+        } else {
+            caps.addCapability(NET_CAPABILITY_NOT_ROAMING);
+        }
+    }
+
     /**
      * Chooses whether to force all connections to go though VPN.
      *
@@ -1344,6 +1404,7 @@
                 }
             }
         }
+        updateCapabilities();
         return true;
     }