Merge "Change NetworkCapabilties administrator UIDs to be stored as int[]."
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index d8a97de..0f48ac2 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -145,16 +145,6 @@
     public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
 
     /**
-     * A temporary hack until SUPL system can get off the legacy APIS.
-     * They do too many network requests and the long list of apps listening
-     * and waking due to the CONNECTIVITY_ACTION broadcast makes it expensive.
-     * Use this broadcast intent instead for SUPL requests.
-     * @hide
-     */
-    public static final String CONNECTIVITY_ACTION_SUPL =
-            "android.net.conn.CONNECTIVITY_CHANGE_SUPL";
-
-    /**
      * The device has connected to a network that has presented a captive
      * portal, which is blocking Internet connectivity. The user was presented
      * with a notification that network sign in is required,
@@ -1517,84 +1507,6 @@
         return null;
     }
 
-    /**
-     * Guess what the network request was trying to say so that the resulting
-     * network is accessible via the legacy (deprecated) API such as
-     * requestRouteToHost.
-     *
-     * This means we should try to be fairly precise about transport and
-     * capability but ignore things such as networkSpecifier.
-     * If the request has more than one transport or capability it doesn't
-     * match the old legacy requests (they selected only single transport/capability)
-     * so this function cannot map the request to a single legacy type and
-     * the resulting network will not be available to the legacy APIs.
-     *
-     * This code is only called from the requestNetwork API (L and above).
-     *
-     * Setting a legacy type causes CONNECTIVITY_ACTION broadcasts, which are expensive
-     * because they wake up lots of apps - see http://b/23350688 . So we currently only
-     * do this for SUPL requests, which are the only ones that we know need it. If
-     * omitting these broadcasts causes unacceptable app breakage, then for backwards
-     * compatibility we can send them:
-     *
-     * if (targetSdkVersion < Build.VERSION_CODES.M) &&        // legacy API unsupported >= M
-     *     targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP))  // requestNetwork not present < L
-     *
-     * TODO - This should be removed when the legacy APIs are removed.
-     */
-    private int inferLegacyTypeForNetworkCapabilities(NetworkCapabilities netCap) {
-        if (netCap == null) {
-            return TYPE_NONE;
-        }
-
-        if (!netCap.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
-            return TYPE_NONE;
-        }
-
-        // Do this only for SUPL, until GnssLocationProvider is fixed. http://b/25876485 .
-        if (!netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
-            // NOTE: if this causes app breakage, we should not just comment out this early return;
-            // instead, we should make this early return conditional on the requesting app's target
-            // SDK version, as described in the comment above.
-            return TYPE_NONE;
-        }
-
-        String type = null;
-        int result = TYPE_NONE;
-
-        if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
-            type = "enableCBS";
-            result = TYPE_MOBILE_CBS;
-        } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
-            type = "enableIMS";
-            result = TYPE_MOBILE_IMS;
-        } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
-            type = "enableFOTA";
-            result = TYPE_MOBILE_FOTA;
-        } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
-            type = "enableDUN";
-            result = TYPE_MOBILE_DUN;
-        } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
-            type = "enableSUPL";
-            result = TYPE_MOBILE_SUPL;
-        // back out this hack for mms as they no longer need this and it's causing
-        // device slowdowns - b/23350688 (note, supl still needs this)
-        //} else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
-        //    type = "enableMMS";
-        //    result = TYPE_MOBILE_MMS;
-        } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
-            type = "enableHIPRI";
-            result = TYPE_MOBILE_HIPRI;
-        }
-        if (type != null) {
-            NetworkCapabilities testCap = networkCapabilitiesForFeature(TYPE_MOBILE, type);
-            if (testCap.equalsNetCapabilities(netCap) && testCap.equalsTransportTypes(netCap)) {
-                return result;
-            }
-        }
-        return TYPE_NONE;
-    }
-
     private int legacyTypeForNetworkCapabilities(NetworkCapabilities netCap) {
         if (netCap == null) return TYPE_NONE;
         if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
@@ -2576,13 +2488,13 @@
             }
 
             @Override
-            public void onTetheringFailed(final int resultCode) {
+            public void onTetheringFailed(final int error) {
                 callback.onTetheringFailed();
             }
         };
 
         final TetheringRequest request = new TetheringRequest.Builder(type)
-                .setSilentProvisioning(!showProvisioningUi).build();
+                .setShouldShowEntitlementUi(showProvisioningUi).build();
 
         mTetheringManager.startTethering(request, executor, tetheringCallback);
     }
@@ -2802,11 +2714,12 @@
     public static final int TETHER_ERROR_UNAVAIL_IFACE =
             TetheringManager.TETHER_ERROR_UNAVAIL_IFACE;
     /**
-     * @deprecated Use {@link TetheringManager#TETHER_ERROR_MASTER_ERROR}.
+     * @deprecated Use {@link TetheringManager#TETHER_ERROR_INTERNAL_ERROR}.
      * {@hide}
      */
     @Deprecated
-    public static final int TETHER_ERROR_MASTER_ERROR = TetheringManager.TETHER_ERROR_MASTER_ERROR;
+    public static final int TETHER_ERROR_MASTER_ERROR =
+            TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
     /**
      * @deprecated Use {@link TetheringManager#TETHER_ERROR_TETHER_IFACE_ERROR}.
      * {@hide}
@@ -2822,19 +2735,19 @@
     public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR =
             TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
     /**
-     * @deprecated Use {@link TetheringManager#TETHER_ERROR_ENABLE_NAT_ERROR}.
+     * @deprecated Use {@link TetheringManager#TETHER_ERROR_ENABLE_FORWARDING_ERROR}.
      * {@hide}
      */
     @Deprecated
     public static final int TETHER_ERROR_ENABLE_NAT_ERROR =
-            TetheringManager.TETHER_ERROR_ENABLE_NAT_ERROR;
+            TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR;
     /**
-     * @deprecated Use {@link TetheringManager#TETHER_ERROR_DISABLE_NAT_ERROR}.
+     * @deprecated Use {@link TetheringManager#TETHER_ERROR_DISABLE_FORWARDING_ERROR}.
      * {@hide}
      */
     @Deprecated
     public static final int TETHER_ERROR_DISABLE_NAT_ERROR =
-            TetheringManager.TETHER_ERROR_DISABLE_NAT_ERROR;
+            TetheringManager.TETHER_ERROR_DISABLE_FORWARDING_ERROR;
     /**
      * @deprecated Use {@link TetheringManager#TETHER_ERROR_IFACE_CFG_ERROR}.
      * {@hide}
@@ -2843,13 +2756,13 @@
     public static final int TETHER_ERROR_IFACE_CFG_ERROR =
             TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
     /**
-     * @deprecated Use {@link TetheringManager#TETHER_ERROR_PROVISION_FAILED}.
+     * @deprecated Use {@link TetheringManager#TETHER_ERROR_PROVISIONING_FAILED}.
      * {@hide}
      */
     @SystemApi
     @Deprecated
     public static final int TETHER_ERROR_PROVISION_FAILED =
-            TetheringManager.TETHER_ERROR_PROVISION_FAILED;
+            TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
     /**
      * @deprecated Use {@link TetheringManager#TETHER_ERROR_DHCPSERVER_ERROR}.
      * {@hide}
@@ -2881,7 +2794,14 @@
     @UnsupportedAppUsage
     @Deprecated
     public int getLastTetherError(String iface) {
-        return mTetheringManager.getLastTetherError(iface);
+        int error = mTetheringManager.getLastTetherError(iface);
+        if (error == TetheringManager.TETHER_ERROR_UNKNOWN_TYPE) {
+            // TETHER_ERROR_UNKNOWN_TYPE was introduced with TetheringManager and has never been
+            // returned by ConnectivityManager. Convert it to the legacy TETHER_ERROR_UNKNOWN_IFACE
+            // instead.
+            error = TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
+        }
+        return error;
     }
 
     /** @hide */
@@ -3774,29 +3694,29 @@
     /**
      * Helper function to request a network with a particular legacy type.
      *
-     * @deprecated This is temporarily public for tethering to backwards compatibility that uses
-     * the NetworkRequest API to request networks with legacy type and relies on
-     * CONNECTIVITY_ACTION broadcasts instead of NetworkCallbacks. New caller should use
+     * This API is only for use in internal system code that requests networks with legacy type and
+     * relies on CONNECTIVITY_ACTION broadcasts instead of NetworkCallbacks. New caller should use
      * {@link #requestNetwork(NetworkRequest, NetworkCallback, Handler)} instead.
      *
-     * TODO: update said system code to rely on NetworkCallbacks and make this method private.
-
      * @param request {@link NetworkRequest} describing this request.
-     * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
-     *                        the callback must not be shared - it uniquely specifies this request.
      * @param timeoutMs The time in milliseconds to attempt looking for a suitable network
      *                  before {@link NetworkCallback#onUnavailable()} is called. The timeout must
      *                  be a positive value (i.e. >0).
      * @param legacyType to specify the network type(#TYPE_*).
      * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
+     *                        the callback must not be shared - it uniquely specifies this request.
      *
      * @hide
      */
     @SystemApi
-    @Deprecated
+    @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
     public void requestNetwork(@NonNull NetworkRequest request,
-            @NonNull NetworkCallback networkCallback, int timeoutMs, int legacyType,
-            @NonNull Handler handler) {
+            int timeoutMs, int legacyType, @NonNull Handler handler,
+            @NonNull NetworkCallback networkCallback) {
+        if (legacyType == TYPE_NONE) {
+            throw new IllegalArgumentException("TYPE_NONE is meaningless legacy type");
+        }
         CallbackHandler cbHandler = new CallbackHandler(handler);
         NetworkCapabilities nc = request.networkCapabilities;
         sendRequestForNetwork(nc, networkCallback, timeoutMs, REQUEST, legacyType, cbHandler);
@@ -3894,9 +3814,9 @@
      */
     public void requestNetwork(@NonNull NetworkRequest request,
             @NonNull NetworkCallback networkCallback, @NonNull Handler handler) {
-        int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
         CallbackHandler cbHandler = new CallbackHandler(handler);
-        requestNetwork(request, networkCallback, 0, legacyType, cbHandler);
+        NetworkCapabilities nc = request.networkCapabilities;
+        sendRequestForNetwork(nc, networkCallback, 0, REQUEST, TYPE_NONE, cbHandler);
     }
 
     /**
@@ -3929,8 +3849,9 @@
     public void requestNetwork(@NonNull NetworkRequest request,
             @NonNull NetworkCallback networkCallback, int timeoutMs) {
         checkTimeout(timeoutMs);
-        int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
-        requestNetwork(request, networkCallback, timeoutMs, legacyType, getDefaultHandler());
+        NetworkCapabilities nc = request.networkCapabilities;
+        sendRequestForNetwork(nc, networkCallback, timeoutMs, REQUEST, TYPE_NONE,
+                getDefaultHandler());
     }
 
     /**
@@ -3955,9 +3876,9 @@
     public void requestNetwork(@NonNull NetworkRequest request,
             @NonNull NetworkCallback networkCallback, @NonNull Handler handler, int timeoutMs) {
         checkTimeout(timeoutMs);
-        int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
         CallbackHandler cbHandler = new CallbackHandler(handler);
-        requestNetwork(request, networkCallback, timeoutMs, legacyType, cbHandler);
+        NetworkCapabilities nc = request.networkCapabilities;
+        sendRequestForNetwork(nc, networkCallback, timeoutMs, REQUEST, TYPE_NONE, cbHandler);
     }
 
     /**
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index fe3f919..2c356e4 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -686,17 +686,29 @@
             route.getDestination(),
             route.getGateway(),
             mIfaceName,
-            route.getType());
+            route.getType(),
+            route.getMtu());
+    }
+
+    private int findRouteIndexByDestination(RouteInfo route) {
+        for (int i = 0; i < mRoutes.size(); i++) {
+            if (mRoutes.get(i).isSameDestinationAs(route)) {
+                return i;
+            }
+        }
+        return -1;
     }
 
     /**
-     * Adds a {@link RouteInfo} to this {@code LinkProperties}, if not present. If the
-     * {@link RouteInfo} had an interface name set and that differs from the interface set for this
-     * {@code LinkProperties} an {@link IllegalArgumentException} will be thrown.  The proper
+     * Adds a {@link RouteInfo} to this {@code LinkProperties}, if a {@link RouteInfo}
+     * with the same destination exists with different properties (e.g., different MTU),
+     * it will be updated. If the {@link RouteInfo} had an interface name set and
+     * that differs from the interface set for this {@code LinkProperties} an
+     * {@link IllegalArgumentException} will be thrown.  The proper
      * course is to add either un-named or properly named {@link RouteInfo}.
      *
      * @param route A {@link RouteInfo} to add to this object.
-     * @return {@code false} if the route was already present, {@code true} if it was added.
+     * @return {@code true} was added or updated, false otherwise.
      */
     public boolean addRoute(@NonNull RouteInfo route) {
         String routeIface = route.getInterface();
@@ -706,11 +718,20 @@
                             + " vs. " + mIfaceName);
         }
         route = routeWithInterface(route);
-        if (!mRoutes.contains(route)) {
+
+        int i = findRouteIndexByDestination(route);
+        if (i == -1) {
+            // Route was not present. Add it.
             mRoutes.add(route);
             return true;
+        } else if (mRoutes.get(i).equals(route)) {
+            // Route was present and has same properties. Do nothing.
+            return false;
+        } else {
+            // Route was present and has different properties. Update it.
+            mRoutes.set(i, route);
+            return true;
         }
-        return false;
     }
 
     /**
@@ -718,6 +739,7 @@
      * specify an interface and the interface must match the interface of this
      * {@code LinkProperties}, or it will not be removed.
      *
+     * @param route A {@link RouteInfo} specifying the route to remove.
      * @return {@code true} if the route was removed, {@code false} if it was not present.
      *
      * @hide
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index 2b9e9fe..fec2df4 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -527,6 +527,26 @@
     }
 
     /**
+     * Compares this RouteInfo object against the specified object and indicates if the
+     * destinations of both routes are equal.
+     * @return {@code true} if the route destinations are equal, {@code false} otherwise.
+     *
+     * @hide
+     */
+    public boolean isSameDestinationAs(@Nullable Object obj) {
+        if (this == obj) return true;
+
+        if (!(obj instanceof RouteInfo)) return false;
+
+        RouteInfo target = (RouteInfo) obj;
+
+        if (Objects.equals(mDestination, target.getDestination())) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
      *  Returns a hashcode for this <code>RouteInfo</code> object.
      */
     public int hashCode() {
diff --git a/core/java/android/net/TestNetworkManager.java b/core/java/android/net/TestNetworkManager.java
index c3284df..a0a563b 100644
--- a/core/java/android/net/TestNetworkManager.java
+++ b/core/java/android/net/TestNetworkManager.java
@@ -30,6 +30,18 @@
  */
 @TestApi
 public class TestNetworkManager {
+    /**
+     * Prefix for tun interfaces created by this class.
+     * @hide
+     */
+    public static final String TEST_TUN_PREFIX = "testtun";
+
+    /**
+     * Prefix for tap interfaces created by this class.
+     * @hide
+     */
+    public static final String TEST_TAP_PREFIX = "testtap";
+
     @NonNull private static final String TAG = TestNetworkManager.class.getSimpleName();
 
     @NonNull private final ITestNetworkManager mService;
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 32ff529..1ec4bc3 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -112,6 +112,7 @@
 import android.net.PrivateDnsConfigParcel;
 import android.net.ProxyInfo;
 import android.net.RouteInfo;
+import android.net.RouteInfoParcel;
 import android.net.SocketKeepalive;
 import android.net.TetheringManager;
 import android.net.UidRange;
@@ -122,6 +123,7 @@
 import android.net.metrics.NetworkEvent;
 import android.net.netlink.InetDiagMessage;
 import android.net.shared.PrivateDnsConfig;
+import android.net.util.LinkPropertiesUtils.CompareOrUpdateResult;
 import android.net.util.LinkPropertiesUtils.CompareResult;
 import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.NetdService;
@@ -234,6 +236,7 @@
 import java.util.StringJoiner;
 import java.util.TreeSet;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
 
 /**
  * @hide
@@ -2230,14 +2233,9 @@
             if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
                 final NetworkInfo ni = intent.getParcelableExtra(
                         ConnectivityManager.EXTRA_NETWORK_INFO);
-                if (ni.getType() == ConnectivityManager.TYPE_MOBILE_SUPL) {
-                    intent.setAction(ConnectivityManager.CONNECTIVITY_ACTION_SUPL);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                } else {
-                    BroadcastOptions opts = BroadcastOptions.makeBasic();
-                    opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.M);
-                    options = opts.toBundle();
-                }
+                final BroadcastOptions opts = BroadcastOptions.makeBasic();
+                opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.M);
+                options = opts.toBundle();
                 final IBatteryStats bs = mDeps.getBatteryStatsService();
                 try {
                     bs.noteConnectivityChanged(intent.getIntExtra(
@@ -5395,6 +5393,9 @@
     public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
             Messenger messenger, int timeoutMs, IBinder binder, int legacyType,
             @NonNull String callingPackageName) {
+        if (legacyType != TYPE_NONE && !checkNetworkStackPermission()) {
+            throw new SecurityException("Insufficient permissions to specify legacy type");
+        }
         final int callingUid = Binder.getCallingUid();
         final NetworkRequest.Type type = (networkCapabilities == null)
                 ? NetworkRequest.Type.TRACK_DEFAULT
@@ -5944,15 +5945,49 @@
         }
     }
 
+    // TODO: move to frameworks/libs/net.
+    private RouteInfoParcel convertRouteInfo(RouteInfo route) {
+        final String nextHop;
+
+        switch (route.getType()) {
+            case RouteInfo.RTN_UNICAST:
+                if (route.hasGateway()) {
+                    nextHop = route.getGateway().getHostAddress();
+                } else {
+                    nextHop = INetd.NEXTHOP_NONE;
+                }
+                break;
+            case RouteInfo.RTN_UNREACHABLE:
+                nextHop = INetd.NEXTHOP_UNREACHABLE;
+                break;
+            case RouteInfo.RTN_THROW:
+                nextHop = INetd.NEXTHOP_THROW;
+                break;
+            default:
+                nextHop = INetd.NEXTHOP_NONE;
+                break;
+        }
+
+        final RouteInfoParcel rip = new RouteInfoParcel();
+        rip.ifName = route.getInterface();
+        rip.destination = route.getDestination().toString();
+        rip.nextHop = nextHop;
+        rip.mtu = route.getMtu();
+
+        return rip;
+    }
+
     /**
      * Have netd update routes from oldLp to newLp.
      * @return true if routes changed between oldLp and newLp
      */
     private boolean updateRoutes(LinkProperties newLp, LinkProperties oldLp, int netId) {
-        // Compare the route diff to determine which routes should be added and removed.
-        CompareResult<RouteInfo> routeDiff = new CompareResult<>(
+        Function<RouteInfo, IpPrefix> getDestination = (r) -> r.getDestination();
+        // compare the route diff to determine which routes have been updated
+        CompareOrUpdateResult<IpPrefix, RouteInfo> routeDiff = new CompareOrUpdateResult<>(
                 oldLp != null ? oldLp.getAllRoutes() : null,
-                newLp != null ? newLp.getAllRoutes() : null);
+                newLp != null ? newLp.getAllRoutes() : null,
+                getDestination);
 
         // add routes before removing old in case it helps with continuous connectivity
 
@@ -5961,10 +5996,10 @@
             if (route.hasGateway()) continue;
             if (VDBG || DDBG) log("Adding Route [" + route + "] to network " + netId);
             try {
-                mNMS.addRoute(netId, route);
+                mNetd.networkAddRouteParcel(netId, convertRouteInfo(route));
             } catch (Exception e) {
                 if ((route.getDestination().getAddress() instanceof Inet4Address) || VDBG) {
-                    loge("Exception in addRoute for non-gateway: " + e);
+                    loge("Exception in networkAddRouteParcel for non-gateway: " + e);
                 }
             }
         }
@@ -5972,10 +6007,10 @@
             if (!route.hasGateway()) continue;
             if (VDBG || DDBG) log("Adding Route [" + route + "] to network " + netId);
             try {
-                mNMS.addRoute(netId, route);
+                mNetd.networkAddRouteParcel(netId, convertRouteInfo(route));
             } catch (Exception e) {
                 if ((route.getGateway() instanceof Inet4Address) || VDBG) {
-                    loge("Exception in addRoute for gateway: " + e);
+                    loge("Exception in networkAddRouteParcel for gateway: " + e);
                 }
             }
         }
@@ -5983,12 +6018,22 @@
         for (RouteInfo route : routeDiff.removed) {
             if (VDBG || DDBG) log("Removing Route [" + route + "] from network " + netId);
             try {
-                mNMS.removeRoute(netId, route);
+                mNetd.networkRemoveRouteParcel(netId, convertRouteInfo(route));
             } catch (Exception e) {
-                loge("Exception in removeRoute: " + e);
+                loge("Exception in networkRemoveRouteParcel: " + e);
             }
         }
-        return !routeDiff.added.isEmpty() || !routeDiff.removed.isEmpty();
+
+        for (RouteInfo route : routeDiff.updated) {
+            if (VDBG || DDBG) log("Updating Route [" + route + "] from network " + netId);
+            try {
+                mNetd.networkUpdateRouteParcel(netId, convertRouteInfo(route));
+            } catch (Exception e) {
+                loge("Exception in networkUpdateRouteParcel: " + e);
+            }
+        }
+        return !routeDiff.added.isEmpty() || !routeDiff.removed.isEmpty()
+                || !routeDiff.updated.isEmpty();
     }
 
     private void updateDnses(LinkProperties newLp, LinkProperties oldLp, int netId) {
@@ -7897,10 +7942,13 @@
             return false;
         }
 
+        final Network[] underlyingNetworks;
         synchronized (mVpns) {
-            if (getVpnIfOwner(callbackUid) != null) {
-                return true;
-            }
+            final Vpn vpn = getVpnIfOwner(callbackUid);
+            underlyingNetworks = (vpn == null) ? null : vpn.getUnderlyingNetworks();
+        }
+        if (underlyingNetworks != null) {
+            if (Arrays.asList(underlyingNetworks).contains(nai.network)) return true;
         }
 
         // Administrator UIDs also contains the Owner UID
diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java
index 81a1372..0ea7346 100644
--- a/services/core/java/com/android/server/TestNetworkService.java
+++ b/services/core/java/com/android/server/TestNetworkService.java
@@ -16,6 +16,9 @@
 
 package com.android.server;
 
+import static android.net.TestNetworkManager.TEST_TAP_PREFIX;
+import static android.net.TestNetworkManager.TEST_TUN_PREFIX;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -60,8 +63,6 @@
 class TestNetworkService extends ITestNetworkManager.Stub {
     @NonNull private static final String TAG = TestNetworkService.class.getSimpleName();
     @NonNull private static final String TEST_NETWORK_TYPE = "TEST_NETWORK";
-    @NonNull private static final String TEST_TUN_PREFIX = "testtun";
-    @NonNull private static final String TEST_TAP_PREFIX = "testtap";
     @NonNull private static final AtomicInteger sTestTunIndex = new AtomicInteger();
 
     @NonNull private final Context mContext;
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index efea91a..9fe1883 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -48,9 +48,11 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.os.Build;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArraySet;
 
+import androidx.core.os.BuildCompat;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
@@ -64,6 +66,13 @@
     private static final String TEST_SSID = "TEST_SSID";
     private static final String DIFFERENT_TEST_SSID = "DIFFERENT_TEST_SSID";
 
+    private boolean isAtLeastR() {
+        // BuildCompat.isAtLeastR() is used to check the Android version before releasing Android R.
+        // Build.VERSION.SDK_INT > Build.VERSION_CODES.Q is used to check the Android version after
+        // releasing Android R.
+        return BuildCompat.isAtLeastR() || Build.VERSION.SDK_INT > Build.VERSION_CODES.Q;
+    }
+
     @Test
     public void testMaybeMarkCapabilitiesRestricted() {
         // verify EIMS is restricted
@@ -269,25 +278,36 @@
             .setUids(uids)
             .addCapability(NET_CAPABILITY_EIMS)
             .addCapability(NET_CAPABILITY_NOT_METERED);
-        netCap.setOwnerUid(123);
+        if (isAtLeastR()) {
+            netCap.setOwnerUid(123);
+        }
         assertParcelingIsLossless(netCap);
         netCap.setSSID(TEST_SSID);
-        assertParcelSane(netCap, 15);
+        testParcelSane(netCap);
     }
 
     @Test
     public void testParcelNetworkCapabilitiesWithRequestorUidAndPackageName() {
         final NetworkCapabilities netCap = new NetworkCapabilities()
                 .addCapability(NET_CAPABILITY_INTERNET)
-                .setRequestorUid(9304)
-                .setRequestorPackageName("com.android.test")
                 .addCapability(NET_CAPABILITY_EIMS)
                 .addCapability(NET_CAPABILITY_NOT_METERED);
+        if (isAtLeastR()) {
+            netCap.setRequestorPackageName("com.android.test");
+            netCap.setRequestorUid(9304);
+        }
         assertParcelingIsLossless(netCap);
         netCap.setSSID(TEST_SSID);
-        assertParcelSane(netCap, 15);
+        testParcelSane(netCap);
     }
 
+    private void testParcelSane(NetworkCapabilities cap) {
+        if (isAtLeastR()) {
+            assertParcelSane(cap, 15);
+        } else {
+            assertParcelSane(cap, 11);
+        }
+    }
 
     @Test
     public void testOemPaid() {
diff --git a/tests/net/java/android/net/NetworkTemplateTest.kt b/tests/net/java/android/net/NetworkTemplateTest.kt
new file mode 100644
index 0000000..5dd0fda
--- /dev/null
+++ b/tests/net/java/android/net/NetworkTemplateTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2020 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 android.net
+
+import android.content.Context
+import android.net.ConnectivityManager.TYPE_MOBILE
+import android.net.ConnectivityManager.TYPE_WIFI
+import android.net.NetworkIdentity.SUBTYPE_COMBINED
+import android.net.NetworkIdentity.buildNetworkIdentity
+import android.net.NetworkStats.DEFAULT_NETWORK_ALL
+import android.net.NetworkStats.METERED_ALL
+import android.net.NetworkStats.ROAMING_ALL
+import android.net.NetworkTemplate.MATCH_MOBILE
+import android.net.NetworkTemplate.MATCH_WIFI
+import android.net.NetworkTemplate.NETWORK_TYPE_ALL
+import android.net.NetworkTemplate.buildTemplateMobileWithRatType
+import android.telephony.TelephonyManager
+import com.android.testutils.assertParcelSane
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertTrue
+
+private const val TEST_IMSI1 = "imsi1"
+private const val TEST_IMSI2 = "imsi2"
+private const val TEST_SSID1 = "ssid1"
+
+@RunWith(JUnit4::class)
+class NetworkTemplateTest {
+    private val mockContext = mock(Context::class.java)
+
+    private fun buildMobileNetworkState(subscriberId: String): NetworkState =
+            buildNetworkState(TYPE_MOBILE, subscriberId = subscriberId)
+    private fun buildWifiNetworkState(ssid: String): NetworkState =
+            buildNetworkState(TYPE_WIFI, ssid = ssid)
+
+    private fun buildNetworkState(
+        type: Int,
+        subscriberId: String? = null,
+        ssid: String? = null
+    ): NetworkState {
+        val info = mock(NetworkInfo::class.java)
+        doReturn(type).`when`(info).type
+        doReturn(NetworkInfo.State.CONNECTED).`when`(info).state
+        val lp = LinkProperties()
+        val caps = NetworkCapabilities().apply {
+            setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false)
+            setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true)
+        }
+        return NetworkState(info, lp, caps, mock(Network::class.java), subscriberId, ssid)
+    }
+
+    private fun NetworkTemplate.assertMatches(ident: NetworkIdentity) =
+            assertTrue(matches(ident), "$this does not match $ident")
+
+    private fun NetworkTemplate.assertDoesNotMatch(ident: NetworkIdentity) =
+            assertFalse(matches(ident), "$this should match $ident")
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+    }
+
+    @Test
+    fun testRatTypeGroupMatches() {
+        val stateMobile = buildMobileNetworkState(TEST_IMSI1)
+        // Build UMTS template that matches mobile identities with RAT in the same
+        // group with any IMSI. See {@link NetworkTemplate#getCollapsedRatType}.
+        val templateUmts = buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_UMTS)
+        // Build normal template that matches mobile identities with any RAT and IMSI.
+        val templateAll = buildTemplateMobileWithRatType(null, NETWORK_TYPE_ALL)
+        // Build template with UNKNOWN RAT that matches mobile identities with RAT that
+        // cannot be determined.
+        val templateUnknown =
+                buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_UNKNOWN)
+
+        val identUmts = buildNetworkIdentity(
+                mockContext, stateMobile, false, TelephonyManager.NETWORK_TYPE_UMTS)
+        val identHsdpa = buildNetworkIdentity(
+                mockContext, stateMobile, false, TelephonyManager.NETWORK_TYPE_HSDPA)
+        val identLte = buildNetworkIdentity(
+                mockContext, stateMobile, false, TelephonyManager.NETWORK_TYPE_LTE)
+        val identCombined = buildNetworkIdentity(
+                mockContext, stateMobile, false, SUBTYPE_COMBINED)
+        val identImsi2 = buildNetworkIdentity(mockContext, buildMobileNetworkState(TEST_IMSI2),
+                false, TelephonyManager.NETWORK_TYPE_UMTS)
+        val identWifi = buildNetworkIdentity(
+                mockContext, buildWifiNetworkState(TEST_SSID1), true, 0)
+
+        // Assert that identity with the same RAT matches.
+        templateUmts.assertMatches(identUmts)
+        templateAll.assertMatches(identUmts)
+        templateUnknown.assertDoesNotMatch(identUmts)
+        // Assert that identity with the RAT within the same group matches.
+        templateUmts.assertMatches(identHsdpa)
+        templateAll.assertMatches(identHsdpa)
+        templateUnknown.assertDoesNotMatch(identHsdpa)
+        // Assert that identity with the RAT out of the same group only matches template with
+        // NETWORK_TYPE_ALL.
+        templateUmts.assertDoesNotMatch(identLte)
+        templateAll.assertMatches(identLte)
+        templateUnknown.assertDoesNotMatch(identLte)
+        // Assert that identity with combined RAT only matches with template with NETWORK_TYPE_ALL
+        // and NETWORK_TYPE_UNKNOWN.
+        templateUmts.assertDoesNotMatch(identCombined)
+        templateAll.assertMatches(identCombined)
+        templateUnknown.assertMatches(identCombined)
+        // Assert that identity with different IMSI matches.
+        templateUmts.assertMatches(identImsi2)
+        templateAll.assertMatches(identImsi2)
+        templateUnknown.assertDoesNotMatch(identImsi2)
+        // Assert that wifi identity does not match.
+        templateUmts.assertDoesNotMatch(identWifi)
+        templateAll.assertDoesNotMatch(identWifi)
+        templateUnknown.assertDoesNotMatch(identWifi)
+    }
+
+    @Test
+    fun testParcelUnparcel() {
+        val templateMobile = NetworkTemplate(MATCH_MOBILE, TEST_IMSI1, null, null, METERED_ALL,
+                ROAMING_ALL, DEFAULT_NETWORK_ALL, TelephonyManager.NETWORK_TYPE_LTE)
+        val templateWifi = NetworkTemplate(MATCH_WIFI, null, null, TEST_SSID1, METERED_ALL,
+                ROAMING_ALL, DEFAULT_NETWORK_ALL, 0)
+        assertParcelSane(templateMobile, 8)
+        assertParcelSane(templateWifi, 8)
+    }
+
+    // Verify NETWORK_TYPE_ALL does not conflict with TelephonyManager#NETWORK_TYPE_* constants.
+    @Test
+    fun testNetworkTypeAll() {
+        for (ratType in TelephonyManager.getAllNetworkTypes()) {
+            assertNotEquals(NETWORK_TYPE_ALL, ratType)
+        }
+    }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index b775139..0e592d9 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -25,7 +25,6 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
-import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_SUPL;
 import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
 import static android.net.ConnectivityManager.EXTRA_NETWORK_TYPE;
 import static android.net.ConnectivityManager.NETID_UNSET;
@@ -175,6 +174,7 @@
 import android.net.ProxyInfo;
 import android.net.ResolverParamsParcel;
 import android.net.RouteInfo;
+import android.net.RouteInfoParcel;
 import android.net.SocketKeepalive;
 import android.net.UidRange;
 import android.net.Uri;
@@ -307,6 +307,8 @@
 
     private static final long TIMESTAMP = 1234L;
 
+    private static final int NET_ID = 110;
+
     private static final String CLAT_PREFIX = "v4-";
     private static final String MOBILE_IFNAME = "test_rmnet_data0";
     private static final String WIFI_IFNAME = "test_wlan0";
@@ -1016,6 +1018,7 @@
         private int mVpnType = VpnManager.TYPE_VPN_SERVICE;
 
         private VpnInfo mVpnInfo;
+        private Network[] mUnderlyingNetworks;
 
         public MockVpn(int userId) {
             super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService,
@@ -1105,9 +1108,21 @@
             return super.getVpnInfo();
         }
 
-        private void setVpnInfo(VpnInfo vpnInfo) {
+        private synchronized void setVpnInfo(VpnInfo vpnInfo) {
             mVpnInfo = vpnInfo;
         }
+
+        @Override
+        public synchronized Network[] getUnderlyingNetworks() {
+            if (mUnderlyingNetworks != null) return mUnderlyingNetworks;
+
+            return super.getUnderlyingNetworks();
+        }
+
+        /** Don't override behavior for {@link Vpn#setUnderlyingNetworks}. */
+        private synchronized void overrideUnderlyingNetworks(Network[] underlyingNetworks) {
+            mUnderlyingNetworks = underlyingNetworks;
+        }
     }
 
     private void mockVpn(int uid) {
@@ -1374,7 +1389,6 @@
             @NonNull final Predicate<Intent> filter) {
         final ConditionVariable cv = new ConditionVariable();
         final IntentFilter intentFilter = new IntentFilter(CONNECTIVITY_ACTION);
-        intentFilter.addAction(CONNECTIVITY_ACTION_SUPL);
         final BroadcastReceiver receiver = new BroadcastReceiver() {
                     private int remaining = count;
                     public void onReceive(Context context, Intent intent) {
@@ -1422,56 +1436,28 @@
         final NetworkRequest legacyRequest = new NetworkRequest(legacyCaps, TYPE_MOBILE_SUPL,
                 ConnectivityManager.REQUEST_ID_UNSET, NetworkRequest.Type.REQUEST);
 
-        // Send request and check that the legacy broadcast for SUPL is sent correctly.
+        // File request, withdraw it and make sure no broadcast is sent
+        final ConditionVariable cv2 = registerConnectivityBroadcast(1);
         final TestNetworkCallback callback = new TestNetworkCallback();
-        final ConditionVariable cv2 = registerConnectivityBroadcastThat(1,
-                intent -> intent.getIntExtra(EXTRA_NETWORK_TYPE, -1) == TYPE_MOBILE_SUPL);
         mCm.requestNetwork(legacyRequest, callback);
         callback.expectCallback(CallbackEntry.AVAILABLE, mCellNetworkAgent);
-        waitFor(cv2);
-
-        // File another request, withdraw it and make sure no broadcast is sent
-        final ConditionVariable cv3 = registerConnectivityBroadcast(1);
-        final TestNetworkCallback callback2 = new TestNetworkCallback();
-        mCm.requestNetwork(legacyRequest, callback2);
-        callback2.expectCallback(CallbackEntry.AVAILABLE, mCellNetworkAgent);
-        mCm.unregisterNetworkCallback(callback2);
-        assertFalse(cv3.block(800)); // 800ms long enough to at least flake if this is sent
+        mCm.unregisterNetworkCallback(callback);
+        assertFalse(cv2.block(800)); // 800ms long enough to at least flake if this is sent
         // As the broadcast did not fire, the receiver was not unregistered. Do this now.
         mServiceContext.clearRegisteredReceivers();
 
-        // Withdraw the request and check that the broadcast for disconnection is sent.
-        final ConditionVariable cv4 = registerConnectivityBroadcastThat(1, intent ->
-                !((NetworkInfo) intent.getExtra(EXTRA_NETWORK_INFO, -1)).isConnected()
-                        && intent.getIntExtra(EXTRA_NETWORK_TYPE, -1) == TYPE_MOBILE_SUPL);
-        mCm.unregisterNetworkCallback(callback);
-        waitFor(cv4);
-
-        // Re-file the request and expect the connected broadcast again
-        final ConditionVariable cv5 = registerConnectivityBroadcastThat(1,
-                intent -> intent.getIntExtra(EXTRA_NETWORK_TYPE, -1) == TYPE_MOBILE_SUPL);
-        final TestNetworkCallback callback3 = new TestNetworkCallback();
-        mCm.requestNetwork(legacyRequest, callback3);
-        callback3.expectCallback(CallbackEntry.AVAILABLE, mCellNetworkAgent);
-        waitFor(cv5);
-
-        // Disconnect the network and expect two disconnected broadcasts, one for SUPL and one
-        // for mobile. Use a small hack to check that both have been sent, but the order is
-        // not contractual.
+        // Disconnect the network and expect mobile disconnected broadcast. Use a small hack to
+        // check that has been sent.
         final AtomicBoolean vanillaAction = new AtomicBoolean(false);
-        final AtomicBoolean suplAction = new AtomicBoolean(false);
-        final ConditionVariable cv6 = registerConnectivityBroadcastThat(2, intent -> {
+        final ConditionVariable cv3 = registerConnectivityBroadcastThat(1, intent -> {
             if (intent.getAction().equals(CONNECTIVITY_ACTION)) {
                 vanillaAction.set(true);
-            } else if (intent.getAction().equals(CONNECTIVITY_ACTION_SUPL)) {
-                suplAction.set(true);
             }
             return !((NetworkInfo) intent.getExtra(EXTRA_NETWORK_INFO, -1)).isConnected();
         });
         mCellNetworkAgent.disconnect();
-        waitFor(cv6);
+        waitFor(cv3);
         assertTrue(vanillaAction.get());
-        assertTrue(suplAction.get());
     }
 
     @Test
@@ -2464,8 +2450,8 @@
             final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
                     mServiceContext, "testFactory", filter);
             // Register the factory and don't be surprised when the default request arrives.
-            testFactory.register();
             testFactory.expectAddRequestsWithScores(0);
+            testFactory.register();
             testFactory.waitForNetworkRequests(1);
 
             testFactory.setScoreFilter(42);
@@ -6064,6 +6050,7 @@
             verify(mBatteryStatsService).noteNetworkInterfaceType(stackedLp.getInterfaceName(),
                     TYPE_MOBILE);
         }
+        reset(mMockNetd);
 
         // Add ipv4 address, expect that clatd and prefix discovery are stopped and stacked
         // linkproperties are cleaned up.
@@ -6115,7 +6102,6 @@
         networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent);
         verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString());
 
-
         // Clat iface comes up. Expect stacked link to be added.
         clat.interfaceLinkStateChanged(CLAT_PREFIX + MOBILE_IFNAME, true);
         networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
@@ -6701,17 +6687,45 @@
         }
     }
 
+    private void assertRouteInfoParcelMatches(RouteInfo route, RouteInfoParcel parcel) {
+        assertEquals(route.getDestination().toString(), parcel.destination);
+        assertEquals(route.getInterface(), parcel.ifName);
+        assertEquals(route.getMtu(), parcel.mtu);
+
+        switch (route.getType()) {
+            case RouteInfo.RTN_UNICAST:
+                if (route.hasGateway()) {
+                    assertEquals(route.getGateway().getHostAddress(), parcel.nextHop);
+                } else {
+                    assertEquals(INetd.NEXTHOP_NONE, parcel.nextHop);
+                }
+                break;
+            case RouteInfo.RTN_UNREACHABLE:
+                assertEquals(INetd.NEXTHOP_UNREACHABLE, parcel.nextHop);
+                break;
+            case RouteInfo.RTN_THROW:
+                assertEquals(INetd.NEXTHOP_THROW, parcel.nextHop);
+                break;
+            default:
+                assertEquals(INetd.NEXTHOP_NONE, parcel.nextHop);
+                break;
+        }
+    }
+
     private void assertRoutesAdded(int netId, RouteInfo... routes) throws Exception {
-        InOrder inOrder = inOrder(mNetworkManagementService);
+        ArgumentCaptor<RouteInfoParcel> captor = ArgumentCaptor.forClass(RouteInfoParcel.class);
+        verify(mMockNetd, times(routes.length)).networkAddRouteParcel(eq(netId), captor.capture());
         for (int i = 0; i < routes.length; i++) {
-            inOrder.verify(mNetworkManagementService).addRoute(eq(netId), eq(routes[i]));
+            assertRouteInfoParcelMatches(routes[i], captor.getAllValues().get(i));
         }
     }
 
     private void assertRoutesRemoved(int netId, RouteInfo... routes) throws Exception {
-        InOrder inOrder = inOrder(mNetworkManagementService);
+        ArgumentCaptor<RouteInfoParcel> captor = ArgumentCaptor.forClass(RouteInfoParcel.class);
+        verify(mMockNetd, times(routes.length)).networkRemoveRouteParcel(eq(netId),
+                captor.capture());
         for (int i = 0; i < routes.length; i++) {
-            inOrder.verify(mNetworkManagementService).removeRoute(eq(netId), eq(routes[i]));
+            assertRouteInfoParcelMatches(routes[i], captor.getAllValues().get(i));
         }
     }
 
@@ -6822,9 +6836,10 @@
 
     @Test
     public void testCheckConnectivityDiagnosticsPermissionsActiveVpn() throws Exception {
+        final Network network = new Network(NET_ID);
         final NetworkAgentInfo naiWithoutUid =
                 new NetworkAgentInfo(
-                        null, null, null, null, null, new NetworkCapabilities(), 0,
+                        null, null, network, null, null, new NetworkCapabilities(), 0,
                         mServiceContext, null, null, mService, null, null, null, 0);
 
         setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
@@ -6837,11 +6852,19 @@
         info.ownerUid = Process.myUid();
         info.vpnIface = "interface";
         mMockVpn.setVpnInfo(info);
+        mMockVpn.overrideUnderlyingNetworks(new Network[] {network});
         assertTrue(
                 "Active VPN permission not applied",
                 mService.checkConnectivityDiagnosticsPermissions(
                         Process.myPid(), Process.myUid(), naiWithoutUid,
                         mContext.getOpPackageName()));
+
+        mMockVpn.overrideUnderlyingNetworks(null);
+        assertFalse(
+                "VPN shouldn't receive callback on non-underlying network",
+                mService.checkConnectivityDiagnosticsPermissions(
+                        Process.myPid(), Process.myUid(), naiWithoutUid,
+                        mContext.getOpPackageName()));
     }
 
     @Test
@@ -6973,4 +6996,60 @@
         verify(mConnectivityDiagnosticsCallback)
                 .onNetworkConnectivityReported(eq(n), eq(noConnectivity));
     }
+
+    @Test
+    public void testRouteAddDeleteUpdate() throws Exception {
+        final NetworkRequest request = new NetworkRequest.Builder().build();
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        mCm.registerNetworkCallback(request, networkCallback);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        reset(mMockNetd);
+        mCellNetworkAgent.connect(false);
+        networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+        final int netId = mCellNetworkAgent.getNetwork().netId;
+
+        final String iface = "rmnet_data0";
+        final InetAddress gateway = InetAddress.getByName("fe80::5678");
+        RouteInfo direct = RouteInfo.makeHostRoute(gateway, iface);
+        RouteInfo rio1 = new RouteInfo(new IpPrefix("2001:db8:1::/48"), gateway, iface);
+        RouteInfo rio2 = new RouteInfo(new IpPrefix("2001:db8:2::/48"), gateway, iface);
+        RouteInfo defaultRoute = new RouteInfo((IpPrefix) null, gateway, iface);
+        RouteInfo defaultWithMtu = new RouteInfo(null, gateway, iface, RouteInfo.RTN_UNICAST,
+                                                 1280 /* mtu */);
+
+        // Send LinkProperties and check that we ask netd to add routes.
+        LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(iface);
+        lp.addRoute(direct);
+        lp.addRoute(rio1);
+        lp.addRoute(defaultRoute);
+        mCellNetworkAgent.sendLinkProperties(lp);
+        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, x -> x.getRoutes().size() == 3);
+
+        assertRoutesAdded(netId, direct, rio1, defaultRoute);
+        reset(mMockNetd);
+
+        // Send updated LinkProperties and check that we ask netd to add, remove, update routes.
+        assertTrue(lp.getRoutes().contains(defaultRoute));
+        lp.removeRoute(rio1);
+        lp.addRoute(rio2);
+        lp.addRoute(defaultWithMtu);
+        // Ensure adding the same route with a different MTU replaces the previous route.
+        assertFalse(lp.getRoutes().contains(defaultRoute));
+        assertTrue(lp.getRoutes().contains(defaultWithMtu));
+
+        mCellNetworkAgent.sendLinkProperties(lp);
+        networkCallback.expectLinkPropertiesThat(mCellNetworkAgent,
+                x -> x.getRoutes().contains(rio2));
+
+        assertRoutesRemoved(netId, rio1);
+        assertRoutesAdded(netId, rio2);
+
+        ArgumentCaptor<RouteInfoParcel> captor = ArgumentCaptor.forClass(RouteInfoParcel.class);
+        verify(mMockNetd).networkUpdateRouteParcel(eq(netId), captor.capture());
+        assertRouteInfoParcelMatches(defaultWithMtu, captor.getValue());
+
+
+        mCm.unregisterNetworkCallback(networkCallback);
+    }
 }
diff --git a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
index 8f90f13..551498f 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
@@ -319,33 +319,33 @@
             assertEntry(18322, 75, 15031, 75, history.getValues(i++, null));
             assertEntry(527798, 761, 78570, 652, history.getValues(i++, null));
             assertEntry(527797, 760, 78570, 651, history.getValues(i++, null));
-            assertEntry(10747, 50, 16838, 55, history.getValues(i++, null));
-            assertEntry(10747, 49, 16838, 54, history.getValues(i++, null));
+            assertEntry(10747, 50, 16839, 55, history.getValues(i++, null));
+            assertEntry(10747, 49, 16837, 54, history.getValues(i++, null));
             assertEntry(89191, 151, 18021, 140, history.getValues(i++, null));
             assertEntry(89190, 150, 18020, 139, history.getValues(i++, null));
-            assertEntry(3821, 22, 4525, 26, history.getValues(i++, null));
-            assertEntry(3820, 22, 4524, 26, history.getValues(i++, null));
-            assertEntry(91686, 159, 18575, 146, history.getValues(i++, null));
-            assertEntry(91685, 159, 18575, 146, history.getValues(i++, null));
-            assertEntry(8289, 35, 6863, 38, history.getValues(i++, null));
-            assertEntry(8289, 35, 6863, 38, history.getValues(i++, null));
+            assertEntry(3821, 23, 4525, 26, history.getValues(i++, null));
+            assertEntry(3820, 21, 4524, 26, history.getValues(i++, null));
+            assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
+            assertEntry(91685, 159, 18574, 146, history.getValues(i++, null));
+            assertEntry(8289, 36, 6864, 39, history.getValues(i++, null));
+            assertEntry(8289, 34, 6862, 37, history.getValues(i++, null));
             assertEntry(113914, 174, 18364, 157, history.getValues(i++, null));
             assertEntry(113913, 173, 18364, 157, history.getValues(i++, null));
-            assertEntry(11378, 49, 9261, 49, history.getValues(i++, null));
-            assertEntry(11377, 48, 9261, 49, history.getValues(i++, null));
-            assertEntry(201765, 328, 41808, 291, history.getValues(i++, null));
-            assertEntry(201765, 328, 41807, 290, history.getValues(i++, null));
-            assertEntry(106106, 218, 39917, 201, history.getValues(i++, null));
-            assertEntry(106105, 217, 39917, 201, history.getValues(i++, null));
+            assertEntry(11378, 49, 9261, 50, history.getValues(i++, null));
+            assertEntry(11377, 48, 9261, 48, history.getValues(i++, null));
+            assertEntry(201766, 328, 41808, 291, history.getValues(i++, null));
+            assertEntry(201764, 328, 41807, 290, history.getValues(i++, null));
+            assertEntry(106106, 219, 39918, 202, history.getValues(i++, null));
+            assertEntry(106105, 216, 39916, 200, history.getValues(i++, null));
             assertEquals(history.size(), i);
 
             // Slice from middle should be untouched
             history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS,
                     TIME_B + HOUR_IN_MILLIS); i = 0;
-            assertEntry(3821, 22, 4525, 26, history.getValues(i++, null));
-            assertEntry(3820, 22, 4524, 26, history.getValues(i++, null));
-            assertEntry(91686, 159, 18575, 146, history.getValues(i++, null));
-            assertEntry(91685, 159, 18575, 146, history.getValues(i++, null));
+            assertEntry(3821, 23, 4525, 26, history.getValues(i++, null));
+            assertEntry(3820, 21, 4524, 26, history.getValues(i++, null));
+            assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
+            assertEntry(91685, 159, 18574, 146, history.getValues(i++, null));
             assertEquals(history.size(), i);
         }
 
@@ -373,25 +373,25 @@
             assertEntry(527797, 760, 78570, 651, history.getValues(i++, null));
             // Cycle point; start data normalization
             assertEntry(7507, 0, 11763, 0, history.getValues(i++, null));
-            assertEntry(7507, 0, 11763, 0, history.getValues(i++, null));
+            assertEntry(7507, 0, 11762, 0, history.getValues(i++, null));
             assertEntry(62309, 0, 12589, 0, history.getValues(i++, null));
             assertEntry(62309, 0, 12588, 0, history.getValues(i++, null));
             assertEntry(2669, 0, 3161, 0, history.getValues(i++, null));
             assertEntry(2668, 0, 3160, 0, history.getValues(i++, null));
             // Anchor point; end data normalization
-            assertEntry(91686, 159, 18575, 146, history.getValues(i++, null));
-            assertEntry(91685, 159, 18575, 146, history.getValues(i++, null));
-            assertEntry(8289, 35, 6863, 38, history.getValues(i++, null));
-            assertEntry(8289, 35, 6863, 38, history.getValues(i++, null));
+            assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
+            assertEntry(91685, 159, 18574, 146, history.getValues(i++, null));
+            assertEntry(8289, 36, 6864, 39, history.getValues(i++, null));
+            assertEntry(8289, 34, 6862, 37, history.getValues(i++, null));
             assertEntry(113914, 174, 18364, 157, history.getValues(i++, null));
             assertEntry(113913, 173, 18364, 157, history.getValues(i++, null));
             // Cycle point
-            assertEntry(11378, 49, 9261, 49, history.getValues(i++, null));
-            assertEntry(11377, 48, 9261, 49, history.getValues(i++, null));
-            assertEntry(201765, 328, 41808, 291, history.getValues(i++, null));
-            assertEntry(201765, 328, 41807, 290, history.getValues(i++, null));
-            assertEntry(106106, 218, 39917, 201, history.getValues(i++, null));
-            assertEntry(106105, 217, 39917, 201, history.getValues(i++, null));
+            assertEntry(11378, 49, 9261, 50, history.getValues(i++, null));
+            assertEntry(11377, 48, 9261, 48, history.getValues(i++, null));
+            assertEntry(201766, 328, 41808, 291, history.getValues(i++, null));
+            assertEntry(201764, 328, 41807, 290, history.getValues(i++, null));
+            assertEntry(106106, 219, 39918, 202, history.getValues(i++, null));
+            assertEntry(106105, 216, 39916, 200, history.getValues(i++, null));
             assertEquals(history.size(), i);
 
             // Slice from middle should be augmented
@@ -399,8 +399,8 @@
                     TIME_B + HOUR_IN_MILLIS); i = 0;
             assertEntry(2669, 0, 3161, 0, history.getValues(i++, null));
             assertEntry(2668, 0, 3160, 0, history.getValues(i++, null));
-            assertEntry(91686, 159, 18575, 146, history.getValues(i++, null));
-            assertEntry(91685, 159, 18575, 146, history.getValues(i++, null));
+            assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
+            assertEntry(91685, 159, 18574, 146, history.getValues(i++, null));
             assertEquals(history.size(), i);
         }
 
@@ -427,34 +427,34 @@
             assertEntry(527798, 761, 78570, 652, history.getValues(i++, null));
             assertEntry(527797, 760, 78570, 651, history.getValues(i++, null));
             // Cycle point; start data normalization
-            assertEntry(15015, 0, 23526, 0, history.getValues(i++, null));
-            assertEntry(15015, 0, 23526, 0, history.getValues(i++, null));
+            assertEntry(15015, 0, 23527, 0, history.getValues(i++, null));
+            assertEntry(15015, 0, 23524, 0, history.getValues(i++, null));
             assertEntry(124619, 0, 25179, 0, history.getValues(i++, null));
             assertEntry(124618, 0, 25177, 0, history.getValues(i++, null));
             assertEntry(5338, 0, 6322, 0, history.getValues(i++, null));
             assertEntry(5337, 0, 6320, 0, history.getValues(i++, null));
             // Anchor point; end data normalization
-            assertEntry(91686, 159, 18575, 146, history.getValues(i++, null));
-            assertEntry(91685, 159, 18575, 146, history.getValues(i++, null));
-            assertEntry(8289, 35, 6863, 38, history.getValues(i++, null));
-            assertEntry(8289, 35, 6863, 38, history.getValues(i++, null));
+            assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
+            assertEntry(91685, 159, 18574, 146, history.getValues(i++, null));
+            assertEntry(8289, 36, 6864, 39, history.getValues(i++, null));
+            assertEntry(8289, 34, 6862, 37, history.getValues(i++, null));
             assertEntry(113914, 174, 18364, 157, history.getValues(i++, null));
             assertEntry(113913, 173, 18364, 157, history.getValues(i++, null));
             // Cycle point
-            assertEntry(11378, 49, 9261, 49, history.getValues(i++, null));
-            assertEntry(11377, 48, 9261, 49, history.getValues(i++, null));
-            assertEntry(201765, 328, 41808, 291, history.getValues(i++, null));
-            assertEntry(201765, 328, 41807, 290, history.getValues(i++, null));
-            assertEntry(106106, 218, 39917, 201, history.getValues(i++, null));
-            assertEntry(106105, 217, 39917, 201, history.getValues(i++, null));
+            assertEntry(11378, 49, 9261, 50, history.getValues(i++, null));
+            assertEntry(11377, 48, 9261, 48, history.getValues(i++, null));
+            assertEntry(201766, 328, 41808, 291, history.getValues(i++, null));
+            assertEntry(201764, 328, 41807, 290, history.getValues(i++, null));
+            assertEntry(106106, 219, 39918, 202, history.getValues(i++, null));
+            assertEntry(106105, 216, 39916, 200, history.getValues(i++, null));
 
             // Slice from middle should be augmented
             history = getHistory(collection, plan, TIME_B - HOUR_IN_MILLIS,
                     TIME_B + HOUR_IN_MILLIS); i = 0;
             assertEntry(5338, 0, 6322, 0, history.getValues(i++, null));
             assertEntry(5337, 0, 6320, 0, history.getValues(i++, null));
-            assertEntry(91686, 159, 18575, 146, history.getValues(i++, null));
-            assertEntry(91685, 159, 18575, 146, history.getValues(i++, null));
+            assertEntry(91686, 159, 18576, 146, history.getValues(i++, null));
+            assertEntry(91685, 159, 18574, 146, history.getValues(i++, null));
             assertEquals(history.size(), i);
         }
     }
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 6be50c6..6e63313 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -42,6 +42,7 @@
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.NetworkStatsHistory.FIELD_ALL;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import static android.net.NetworkTemplate.buildTemplateMobileWithRatType;
 import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
 import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.net.TrafficStats.UID_REMOVED;
@@ -60,11 +61,13 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
@@ -92,6 +95,8 @@
 import android.os.Messenger;
 import android.os.PowerManager;
 import android.os.SimpleClock;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 
 import androidx.test.InstrumentationRegistry;
@@ -163,11 +168,14 @@
     private @Mock NetworkStatsSettings mSettings;
     private @Mock IBinder mBinder;
     private @Mock AlarmManager mAlarmManager;
+    private @Mock TelephonyManager mTelephonyManager;
     private HandlerThread mHandlerThread;
 
     private NetworkStatsService mService;
     private INetworkStatsSession mSession;
     private INetworkManagementEventObserver mNetworkObserver;
+    @Nullable
+    private PhoneStateListener mPhoneStateListener;
 
     private final Clock mClock = new SimpleClock(ZoneOffset.UTC) {
         @Override
@@ -195,7 +203,7 @@
         mHandlerThread = new HandlerThread("HandlerThread");
         final NetworkStatsService.Dependencies deps = makeDependencies();
         mService = new NetworkStatsService(mServiceContext, mNetManager, mAlarmManager, wakeLock,
-                mClock, mServiceContext.getSystemService(TelephonyManager.class), mSettings,
+                mClock, mTelephonyManager, mSettings,
                 mStatsFactory, new NetworkStatsObservers(), mStatsDir, getBaseDir(mStatsDir), deps);
 
         mElapsedRealtime = 0L;
@@ -216,6 +224,12 @@
                 ArgumentCaptor.forClass(INetworkManagementEventObserver.class);
         verify(mNetManager).registerObserver(networkObserver.capture());
         mNetworkObserver = networkObserver.getValue();
+
+        // Capture the phone state listener that created by service.
+        final ArgumentCaptor<PhoneStateListener> phoneStateListenerCaptor =
+                ArgumentCaptor.forClass(PhoneStateListener.class);
+        verify(mTelephonyManager).listen(phoneStateListenerCaptor.capture(), anyInt());
+        mPhoneStateListener = phoneStateListenerCaptor.getValue();
     }
 
     @NonNull
@@ -534,7 +548,7 @@
     }
 
     @Test
-    public void testUid3g4gCombinedByTemplate() throws Exception {
+    public void testUid3gWimaxCombinedByTemplate() throws Exception {
         // pretend that network comes online
         expectDefaultSettings();
         NetworkState[] states = new NetworkState[] {buildMobile3gState(IMSI_1)};
@@ -558,10 +572,10 @@
         assertUidTotal(sTemplateImsi1, UID_RED, 1024L, 8L, 1024L, 8L, 5);
 
 
-        // now switch over to 4g network
+        // now switch over to wimax network
         incrementCurrentTime(HOUR_IN_MILLIS);
         expectDefaultSettings();
-        states = new NetworkState[] {buildMobile4gState(TEST_IFACE2)};
+        states = new NetworkState[] {buildWimaxState(TEST_IFACE2)};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
                 .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
@@ -589,6 +603,89 @@
     }
 
     @Test
+    public void testMobileStatsByRatType() throws Exception {
+        final NetworkTemplate template3g =
+                buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_UMTS);
+        final NetworkTemplate template4g =
+                buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_LTE);
+        final NetworkTemplate template5g =
+                buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_NR);
+        final NetworkState[] states = new NetworkState[]{buildMobile3gState(IMSI_1)};
+
+        // 3G network comes online.
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
+
+        setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_UMTS);
+        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
+                new VpnInfo[0]);
+
+        // Create some traffic.
+        incrementCurrentTime(MINUTE_IN_MILLIS);
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+                .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE,
+                        12L, 18L, 14L, 1L, 0L)));
+        forcePollAndWaitForIdle();
+
+        // Verify 3g templates gets stats.
+        assertUidTotal(sTemplateImsi1, UID_RED, 12L, 18L, 14L, 1L, 0);
+        assertUidTotal(template3g, UID_RED, 12L, 18L, 14L, 1L, 0);
+        assertUidTotal(template4g, UID_RED, 0L, 0L, 0L, 0L, 0);
+        assertUidTotal(template5g, UID_RED, 0L, 0L, 0L, 0L, 0);
+
+        // 4G network comes online.
+        incrementCurrentTime(MINUTE_IN_MILLIS);
+        setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_LTE);
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+                // Append more traffic on existing 3g stats entry.
+                .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE,
+                        16L, 22L, 17L, 2L, 0L))
+                // Add entry that is new on 4g.
+                .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE,
+                        33L, 27L, 8L, 10L, 1L)));
+        forcePollAndWaitForIdle();
+
+        // Verify ALL_MOBILE template gets all. 3g template counters do not increase.
+        assertUidTotal(sTemplateImsi1, UID_RED, 49L, 49L, 25L, 12L, 1);
+        assertUidTotal(template3g, UID_RED, 12L, 18L, 14L, 1L, 0);
+        // Verify 4g template counts appended stats on existing entry and newly created entry.
+        assertUidTotal(template4g, UID_RED, 4L + 33L, 4L + 27L, 3L + 8L, 1L + 10L, 1);
+        // Verify 5g template doesn't get anything since no traffic is generated on 5g.
+        assertUidTotal(template5g, UID_RED, 0L, 0L, 0L, 0L, 0);
+
+        // 5g network comes online.
+        incrementCurrentTime(MINUTE_IN_MILLIS);
+        setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_NR);
+        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
+                // Existing stats remains.
+                .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE,
+                        16L, 22L, 17L, 2L, 0L))
+                .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE,
+                        33L, 27L, 8L, 10L, 1L))
+                // Add some traffic on 5g.
+                .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE,
+                5L, 13L, 31L, 9L, 2L)));
+        forcePollAndWaitForIdle();
+
+        // Verify ALL_MOBILE template gets all.
+        assertUidTotal(sTemplateImsi1, UID_RED, 54L, 62L, 56L, 21L, 3);
+        // 3g/4g template counters do not increase.
+        assertUidTotal(template3g, UID_RED, 12L, 18L, 14L, 1L, 0);
+        assertUidTotal(template4g, UID_RED, 4L + 33L, 4L + 27L, 3L + 8L, 1L + 10L, 1);
+        // Verify 5g template gets the 5g count.
+        assertUidTotal(template5g, UID_RED, 5L, 13L, 31L, 9L, 2);
+    }
+
+    // TODO: support per IMSI state
+    private void setMobileRatTypeAndWaitForIdle(int ratType) {
+        final ServiceState mockSs = mock(ServiceState.class);
+        when(mockSs.getDataNetworkType()).thenReturn(ratType);
+        mPhoneStateListener.onServiceStateChanged(mockSs);
+
+        HandlerUtilsKt.waitForIdle(mHandlerThread, WAIT_TIMEOUT);
+    }
+
+    @Test
     public void testSummaryForAllUid() throws Exception {
         // pretend that network comes online
         expectDefaultSettings();
@@ -1034,10 +1131,10 @@
         // Create some initial traffic and report to the service.
         incrementCurrentTime(HOUR_IN_MILLIS);
         final NetworkStats expectedStats = new NetworkStats(0L, 1)
-                .addValues(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT,
+                .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT,
                         TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES,
                         128L, 2L, 128L, 2L, 1L))
-                .addValues(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT,
+                .addEntry(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT,
                         0xF00D, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES,
                         64L, 1L, 64L, 1L, 1L));
         cb.notifyStatsUpdated(0 /* unused */, expectedStats, expectedStats);
@@ -1197,6 +1294,7 @@
         when(mSettings.getPollInterval()).thenReturn(HOUR_IN_MILLIS);
         when(mSettings.getPollDelay()).thenReturn(0L);
         when(mSettings.getSampleEnabled()).thenReturn(true);
+        when(mSettings.getCombineSubtypeEnabled()).thenReturn(false);
 
         final Config config = new Config(bucketDuration, deleteAge, deleteAge);
         when(mSettings.getDevConfig()).thenReturn(config);
@@ -1242,6 +1340,7 @@
         final NetworkCapabilities capabilities = new NetworkCapabilities();
         capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, !isMetered);
         capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true);
+        capabilities.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
         return new NetworkState(info, prop, capabilities, WIFI_NETWORK, null, TEST_SSID);
     }
 
@@ -1259,10 +1358,11 @@
         final NetworkCapabilities capabilities = new NetworkCapabilities();
         capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false);
         capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, !isRoaming);
+        capabilities.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
         return new NetworkState(info, prop, capabilities, MOBILE_NETWORK, subscriberId, null);
     }
 
-    private static NetworkState buildMobile4gState(String iface) {
+    private static NetworkState buildWimaxState(@NonNull String iface) {
         final NetworkInfo info = new NetworkInfo(TYPE_WIMAX, 0, null, null);
         info.setDetailedState(DetailedState.CONNECTED, null, null);
         final LinkProperties prop = new LinkProperties();